Friday 22 December 2017

Erlang trading system no Brasil


25 de maio 16:00 GMT (15:00 UTC 17:00 CET 08:00 PDT) A Sportrisq é uma corretora e distribuidora de soluções de gerenciamento de risco e produtos para o setor de esportes. Ouça o CTO Justin Worall descreve o processo de migração de dois componentes principais da plataforma de Python para Erlang, os problemas subjacentes envolvidos, os benefícios percebidos de Erlang nessas situações, o processo de tomada de decisão, os projetos de aplicativos e os resultados. Erlang-solutionsresourceswebinars. html Neste webinar, você aprenderá: O processo de migração de componentes de Python de baixa latência para Erlang O processo de tomada de decisão Desenhos e resultados de aplicativos. Aprenda-lhe alguns Erlang Ei, parece que o Javascript está desativado. Isso é bom, o site funciona sem ele. No entanto, você pode preferir lê-lo com destaque de sintaxe, o que requer Javascript Rage Against The Finite-State Machines Uma máquina de estados finitos (FSM) não é realmente uma máquina, mas tem um número finito de estados. Sempre achei máquinas de estados finitos mais fáceis de entender com gráficos e diagramas. Por exemplo, o seguinte seria um diagrama simplista para um cão (muito burro) como uma máquina de estado: Aqui, o cão tem 3 estados: sentado, latindo ou abanando sua cauda. Diferentes eventos ou entradas podem forçá-lo a mudar seu estado. Se um cão está calmamente sentado e vê um esquilo, ele começará a latir e não vai parar até você acariciá-lo de novo. No entanto, se o cão estiver sentado e você pet, não temos idéia do que pode acontecer. No mundo de Erlang, o cão pode bater (e eventualmente ser reiniciado por seu supervisor). No mundo real, seria um evento estranho, mas seu cão voltaria depois de ser atropelado por um carro, então não é ruim. Heres um diagrama de estado de gatos para uma comparação: este gato tem um único estado, e nenhum evento pode alterá-lo. Implementar a máquina de estado do gato em Erlang é uma tarefa divertida e simples: podemos tentar o módulo para ver que o gato realmente nunca dá uma porcaria: o mesmo pode ser feito para o cão FSM. Exceto que mais estados estejam disponíveis: deve ser relativamente simples combinar cada um dos estados e transições com o que estava no diagrama acima. Heres o FSM em uso: você pode seguir junto com o esquema se você quiser (eu costumo fazer, ajuda a ter certeza de que nada é errado). Esse é o núcleo dos FSMs implementados como processos Erlang. Há coisas que poderiam ter sido feitas de forma diferente: poderíamos ter passado o estado nos argumentos das funções do estado de forma semelhante ao que fazemos com o loop principal dos servidores. Nós também poderíamos ter adicionado funções de inicialização e término, manipulações de código, etc. Outra diferença entre os FSM de cães e gatos é que os eventos dos gatos são síncronos e os eventos dos cães são assíncronos. Em um FSM real, ambos poderiam ser usados ​​de forma mista, mas eu fui para a mais simples representação de pura preguiça inexplorada. Existem outras formas de evento que os exemplos não mostram: eventos globais que podem acontecer em qualquer estado. Um exemplo de tal evento pode ser quando o cão recebe um cheiro de comida. Uma vez que o evento de comida de cheiro é desencadeado, não importa em que estado o cão está, ele vai procurar a fonte de alimento. Agora, não vamos gastar muito tempo implementando tudo isso em nosso FSM escrito em um guardanapo. Em vez disso, vá diretamente para o comportamento genfsm. O comportamento do genfsm é um pouco semelhante ao de genserver, na medida em que é uma versão especializada dele. A maior diferença é que ao invés de lidar com chamadas e lançamentos. Estavam lidando com eventos síncronos e assíncronos. Como nos exemplos de cães e gatos, cada estado é representado por uma função. Novamente, continue com os retornos de chamada que nossos módulos precisam implementar para funcionar. Este é o mesmo init1 usado para servidores genéricos, exceto os valores de retorno aceitos são. . E. A tua de parada funciona da mesma maneira que para o servidor gserver, e hibernar e Timeout mantêm a mesma semântica. O que é novo aqui é essa variável StateName. StateName é um átomo e representa a próxima função de retorno de chamada a ser chamada. As funções StateName2 e StateName3 são nomes de espaço reservado e você deve decidir o que serão. Vamos supor que a função init1 retorna a tupla. Isso significa que a máquina de estados finitos ficará sentada. Este não é o mesmo tipo de estado que vimos com o genserver, é bastante equivalente à sessão. Bark e Wagtail estados do anterior cão FSM. Esses estados determinam um contexto no qual você lida com um determinado evento. Um exemplo disso seria alguém que o chamasse no seu telefone. Se você estiver no estado dormindo em uma manhã de sábado, sua reação pode ser gritar no telefone. Se o seu estado está à espera de uma entrevista de emprego, é provável que você escolha o telefone e responda educadamente. Por outro lado, se você estiver no estado morto, então estou surpreso por você mesmo ler este texto. De volta ao nosso FSM. A função init1 disse que devemos estar sentados. Sempre que o processo genfsm recebe um evento, será chamada a função sitting2 ou sitting3. A função sitting2 é chamada para eventos assíncronos e assento3 para sincronizados. Os argumentos para sitting2 (ou geralmente StateName2) são Event. A mensagem real enviada como um evento e StateData. Os dados que foram transferidos para as chamadas. Sitting2 pode então retornar as tuplas. . E. Os argumentos para sitting3 são semelhantes, exceto que existe uma variável De entre o evento e StateData. A variável From é usada exatamente da mesma forma que para o genserver s, incluindo genfsm: reply2. As funções StateName3 podem retornar as seguintes tuplas: Observe que não existe limite de quantas dessas funções você pode ter, desde que sejam exportadas. Os átomos retornados como NextStateName nas tuplas determinarão se a função será chamada ou não. Na última seção, mencionei eventos globais que desencadeariam uma reação específica, independentemente do estado em que o alimento de cheiro do cão derrubasse o que estava fazendo e buscaria alimentos). Para esses eventos que devem ser tratados da mesma maneira em todos os estados, o retorno de chamada handleevent3 é o que deseja. A função leva argumentos semelhantes a StateName2 com a exceção de que ele aceita uma variável StateName entre eles, informando o que era o estado quando o evento foi recebido. Ele retorna os mesmos valores que StateName2. Handlesyncevent O retorno de chamada handlesyncevent4 é para StateName3 o que handleevent2 é para StateName2. Ele lida com eventos globais síncronos, leva os mesmos parâmetros e retorna o mesmo tipo de tuplas que StateName3. Agora, pode ser um bom momento para explicar como sabemos se um evento é global ou se pretende ser enviado para um estado específico. Para determinar isso, podemos observar a função usada para enviar um evento para o FSM. Eventos assíncronos destinados a qualquer função StateName2 são enviados com sendevent2. Os eventos síncronos a serem capturados pelo StateName3 devem ser enviados com syncsendevent2-3. As duas funções equivalentes para eventos globais são sendallstateevent2 e syncsendallstateevent2-3 (um nome bastante longo). Codechange Isso funciona exatamente o mesmo que para o genserver, exceto que é necessário um parâmetro de estado extra quando chamado como codechange (OldVersion, StateName, Data, Extra). E retorna uma tupla do formulário. Isso deve, novamente, agir um pouco como o que temos para servidores genéricos. Terminate3 deve fazer o oposto do init1. É hora de colocar tudo isso em prática. Muitos tutoriais Erlang sobre máquinas finitas utilizam exemplos que contêm interruptores telefônicos e coisas similares. É meu palpite que a maioria dos programadores raramente terá que lidar com telefones para máquinas de estado. Por isso, iriam olhar para um exemplo que é mais apropriado para muitos desenvolvedores: bem projetar e implementar um sistema de comércio de itens para algum videogame fictício e não existente. O design que escolhi é um pouco desafiador. Ao invés de usar um corretor através do qual os jogadores rodam itens e confirmações (o que, francamente, seria mais fácil), iria implementar um servidor em que ambos os jogadores se falassem diretamente (o que teria a vantagem de ser distribuível). Porque a implementação é complicada, vou passar um bom tempo descrevendo-o, o tipo de problemas a serem enfrentados e as formas de consertá-los. Em primeiro lugar, devemos definir as ações que podem ser feitas pelos nossos jogadores ao negociar. O primeiro está pedindo a criação de um comércio. O outro usuário também deve ser capaz de aceitar esse comércio. Nós não vamos dar-lhes o direito de negar um comércio, no entanto, porque queremos manter as coisas simples. Serão fáceis de adicionar esse recurso uma vez que tudo for feito. Uma vez que o comércio é configurado, nossos usuários devem poder negociar uns com os outros. Isso significa que eles devem ser capazes de fazer ofertas e depois retraí-las se quiserem. Quando ambos os jogadores estão satisfeitos com a oferta, eles podem se declarar todos prontos para finalizar o comércio. Os dados devem então ser guardados em algum lugar dos dois lados. Em qualquer momento, também deve ter sentido para qualquer um dos jogadores cancelar todo o comércio. Alguns pleb poderiam oferecer apenas itens considerados indignos para a outra parte (quem pode estar muito ocupado) e assim deve ser possível revistá-los com um merecido cancelamento. Em suma, as seguintes ações devem ser possíveis: pedir um comércio aceitar uma oferta comercial itens retrair uma oferta declarar-se como pronto cancelar brutalmente o comércio Agora, quando cada uma dessas ações é tomada, os outros jogadores FSM devem ser conscientizados . Isso faz sentido, porque quando Jim diz ao FSM para enviar um item para o Carl, o FSM de Carls deve ser informado disso. Isso significa que ambos os jogadores podem conversar com seus próprios FSM, que falarão com os outros FSM. Isso nos dá algo assim: a primeira coisa a notar quando temos dois processos idênticos que se comunicam entre si é que devemos evitar chamadas síncrona tanto quanto possível. A razão para isso é que, se o Jims FSM envia uma mensagem para o Carls FSM e depois aguarda sua resposta, ao mesmo tempo em que o Carls FSM envia uma mensagem para o Jims FSM e aguarda sua própria resposta específica, ambos acabam esperando o outro Sem responder nunca. Isso efetivamente congela ambos os FSMs. Temos um impasse. Uma solução para isso é aguardar um tempo limite e, em seguida, seguir em frente, mas haverá mensagens sobrantes em ambas as caixas de correio de processos e o protocolo será desarrumado. Isso certamente é uma lata de vermes, e então queremos evitá-lo. A maneira mais simples de fazê-lo é evitar todas as mensagens síncronas e ir totalmente assíncrono. Note que Jim ainda pode fazer uma chamada síncrona para o seu próprio FSM. Não há risco aqui porque o FSM não precisa chamar Jim e, portanto, não pode ocorrer nenhum impasse entre eles. Quando dois desses FSM se comunicam entre si, a troca inteira pode parecer um pouco assim: ambos os FSMs estão no estado ocioso. Quando você pergunta a Jim para trocar, Jim tem que aceitar antes que as coisas mudem. Então, ambos podem oferecer itens ou retirá-los. Quando ambos se declararem prontos, o comércio pode ocorrer. Esta é uma versão simplificada de tudo o que pode acontecer e ver todos os casos possíveis com mais detalhes nos próximos parágrafos. Aqui vem a parte difícil: definir o diagrama de estado e como as transições de estados acontecem. Normalmente, um bom pensamento reflete isso, porque você tem que pensar em todas as pequenas coisas que podem dar errado. Algumas coisas podem dar errado mesmo depois de terem sido revistas muitas vezes. Por isso, eu simplesmente colocarei aquele que eu decidi implementar aqui e depois expliquei. Em primeiro lugar, ambas as máquinas de estados finitos começam no estado ocioso. Neste ponto, uma coisa que podemos fazer é pedir a algum outro jogador que negocie conosco: entramos no modo idlewait para aguardar uma eventual resposta após o FSM encaminhar a demanda. Uma vez que o outro FSM envia a resposta, o nosso pode mudar para negociar: o outro jogador também deve estar em negociação depois disso. Obviamente, se pudermos convidar o outro, o outro pode nos convidar. Se tudo correr bem, isso deve acabar ficando assim: então, isso é praticamente o oposto, como os dois diagramas de estado anteriores agrupados em um. Observe que esperamos que o jogador aceite a oferta neste caso. O que acontece se por pura sorte, pedimos ao outro jogador trocar conosco, ao mesmo tempo que ele nos pede para negociar. O que acontece aqui é que ambos os clientes pedem que seus próprios FSM negociem com o outro. Assim que as mensagens de negociação for enviada, ambos os FSMs mudam para o estado de idlewait. Então eles poderão processar a questão da negociação. Se analisarmos os diagramas de estados anteriores, vemos que essa combinação de eventos é a única vez que recebe boas perguntas de negociação enquanto está no estado de idlewait. Consequentemente, sabemos que obter essas mensagens em idlewait significa que atingimos a condição de corrida e podemos assumir que ambos os usuários querem conversar um com o outro. Podemos mover ambos para negociar o estado. Hooray. Então, agora estavam negociando. De acordo com a lista de ações que listei anteriormente, devemos apoiar os usuários que oferecem itens e, em seguida, retrair a oferta: tudo isso faz é encaminhar a mensagem de nossos clientes para o outro FSM. As duas máquinas de estados finitos precisarão manter uma lista de itens oferecidos por qualquer jogador, para que eles possam atualizar essa lista ao receber essas mensagens. Nós ficamos no estado de negociação depois disso, talvez o outro jogador queira oferecer itens também: Aqui, nosso FSM basicamente age de forma semelhante. Isto é normal. Uma vez que nos cansamos de oferecer coisas e pensamos ser bastante generoso, temos que dizer que estávamos preparados para oficializar o comércio. Porque temos que sincronizar ambos os jogadores, é bom ter que usar um estado intermediário, como fizemos para ocioso e idlewait: o que fazemos aqui é que assim que nosso jogador estiver pronto, nosso FSM pede Jims FSM se estiver pronto. Enquanto aguarda sua resposta, nosso próprio FSM cai em seu estado de espera. A resposta bem conseguida dependerá do estado do Jims FSM: se estiver em espera, isso vai nos dizer que está pronto. Caso contrário, ele vai nos dizer que ainda não está pronto. Isso é precisamente o que nosso FSM responde automaticamente a Jim se ele nos perguntar se estamos prontos quando no estado de negociação: Nossa máquina de estado finito permanecerá no modo de negociação até que nosso jogador diga que está pronto. Vamos assumir que ele fez e agora estava no estado de espera. No entanto, Jims ainda não está disponível. Isso significa que, quando nos declaramos preparados, bem, perguntaram a Jim se ele também estava pronto e seu FSM ainda não responderá: não está pronto, mas nós somos. Nós não podemos fazer muito, mas continuar esperando. Enquanto esperava por Jim, que ainda está negociando, é possível que ele tente nos enviar mais itens ou talvez cancele suas ofertas anteriores: Claro, queremos evitar Jim removendo todos os itens e depois clicando em Estou pronto, Estragando-nos no processo. Assim que ele muda os itens oferecidos, voltamos para negociar o estado para que possamos modificar nossa própria oferta, ou examinar o atual e decidir estar pronto. Enxague e repita. Em algum momento, Jim estará pronto para finalizar o comércio também. Quando isso acontece, sua máquina de estado finito perguntará a nossa se estamos prontos: o que o FSM faz é responder que estamos realmente prontos. Nós ficamos no estado de espera e nos recusamos a mudar para o estado pronto. Por que isso ocorre porque existe uma condição de corrida potencial Imagine que a seguinte seqüência de eventos ocorre, sem fazer este passo necessário: Este é um pouco complexo, então eu explique. Por causa da forma como as mensagens são recebidas, possivelmente só podemos processar a oferta do item depois de nos declararmos preparados e também depois que Jim se declarou pronto. Isso significa que assim que lemos a mensagem da oferta, voltamos para negociar o estado. Durante esse tempo, Jim nos contará que está pronto. Se ele mudasse os estados lá e se movesse para pronto (como ilustrado acima), ele poderia ser pego esperando indefinidamente, enquanto nós não saberíamos o que diabos fazer. Isso também poderia acontecer ao contrário de Ugh. Uma maneira de resolver isso é adicionando uma camada de indireção (Obrigado a David Wheeler). É por isso que ficamos no modo de espera e enviamos pronto (como mostrado em nosso diagrama de estado anterior). Heres como lidamos com essa mensagem pronta, assumindo que já estivemos no estado pronto, porque dissemos ao nosso FSM que estávamos preparados de antemão: quando recebemos pronto dos outros FSM, enviamos de volta novamente. Isto é para se certificar de que não teremos a condição de corrida dupla mencionada acima. Isso criará uma mensagem pronta e supérflua em um dos dois FSMs, mas é preciso ignorá-lo neste caso. Em seguida, enviamos uma mensagem ack (e o Jims FSM fará o mesmo) antes de mudar para o estado pronto. A razão pela qual essa mensagem ack existe é devido a alguns detalhes de implementação sobre sincronização de clientes. Coloquei o diagrama no sentido de ser correto, mas não vou explicar isso até mais tarde. Esqueça disso por enquanto. Finalmente conseguimos sincronizar ambos os jogadores. Whew. Então, agora, o estado pronto. Este é um pouco especial. Ambos os jogadores estão prontos e, basicamente, têm dado às máquinas finitas todo o controle de que precisam. Isso nos permite implementar uma versão bastardizada de um compromisso de duas fases para garantir que as coisas funcionem bem ao fazer o comércio oficial: nossa versão (conforme descrito acima) será bastante simplista. Escrever um commit de duas fases verdadeiramente correto exigiria muito mais código do que o necessário para que possamos entender as máquinas de estados finitos. Finalmente, só temos que permitir que o comércio seja cancelado a qualquer momento. Isso significa que de alguma forma, independentemente do estado em que estavam, iriam ouvir a mensagem de cancelamento dos dois lados e sair da transação. Também deve ser uma cortesia comum para que o outro lado soubesse ter ido antes de sair. Tudo bem É uma grande quantidade de informações para absorver de uma só vez. Não se preocupe se demorar um pouco para compreendê-lo completamente. Levou um monte de pessoas para examinar meu protocolo para ver se estava certo e, mesmo assim, todos nós perdemos algumas condições de corrida que eu peguei alguns dias depois ao revisar o código ao escrever este texto. É normal que precise ler mais de uma vez, especialmente se você não está acostumado a protocolos assíncronos. Se for esse o caso, eu o encorajo inteiramente a tentar projetar seu próprio protocolo. Em seguida, pergunte-se o que acontece se duas pessoas fizerem as mesmas ações de forma muito rápida. O que acontece se eles encadearem outros dois eventos rapidamente? O que eu faço com mensagens que eu não manipulo ao mudar os estados. Você verá que a complexidade cresce de forma muito rápida. Você pode encontrar uma solução semelhante à minha, possivelmente melhor (me avise se este é o caso) Não importa o resultado, é uma coisa muito interessante para trabalhar e nossos FSMs ainda são relativamente simples. Uma vez que você digeriu tudo isso (ou antes, se você for um leitor rebelde), você pode ir para a próxima seção, onde implementamos o sistema de jogos. Por enquanto, você pode fazer uma boa pausa para o café se você quiser fazê-lo. A primeira coisa que precisa ser feita para implementar nosso protocolo com OTPs genfsm é criar a interface. Haverá 3 chamadores para o nosso módulo: o jogador, o comportamento genfsm e os outros jogadores FSM. Só precisamos exportar a função do jogador e as funções genfsm. Isso ocorre porque o outro FSM também será executado dentro do módulo tradefsm e pode acessá-los por dentro: então, essa é a nossa API. Você pode ver Im planejando que algumas funções sejam síncronas e assíncronas. Isso é principalmente porque queremos que nosso cliente nos ligue de forma síncrona em alguns casos, mas o outro FSM pode fazê-lo de forma assíncrona. Ter o cliente síncrono simplifica muito nossa lógica, limitando o número de mensagens contraditórias que podem ser enviadas uma após a outra. Bem, vá lá. Permite primeiro implementar a API pública atual de acordo com o protocolo definido acima: Esta é bastante padrão todas essas funções genfsm foram cobertas antes (exceto start3-4 e startlink3-4 que eu acredito que você pode descobrir) neste capítulo. Em seguida, implemente as funções FSM para FSM. Os primeiros têm a ver com as configurações de comércio, quando queremos primeiro pedir ao outro usuário que se junte a nós em um comércio: a primeira função pede ao outro pid se eles querem trocar e o segundo é usado para responder a ele ( De forma assíncrona, é claro). Podemos então escrever as funções para oferecer e cancelar ofertas. De acordo com o nosso protocolo acima, é o que eles devem ser: então, agora que temos essas chamadas feitas, precisamos nos concentrar no resto. As chamadas restantes dizem respeito a estarem prontas ou não e a manipular o compromisso final. Novamente, dado o nosso protocolo acima, temos três chamadas: você já está. O que pode ter as respostas que não estão disponíveis ou estão prontas. As únicas funções restantes são aquelas que devem ser usadas por ambos os FSMs ao fazer o commit no estado pronto. Seu uso preciso será descrito mais detalhadamente mais adiante, mas, por enquanto, os nomes e o diagrama de diagrama de seqüência de dados anteriores devem ser suficientes. No entanto, você ainda pode transcrevê-los para sua própria versão do tradefsm: Ah, e também a função de cortesia que nos permite avisar o outro FSM, cancelamos o comércio: agora podemos mudar para a parte realmente interessante: as devoluções de retorno genfsm. O primeiro retorno de chamada é init1. No nosso caso, bem, quer que cada FSM segure um nome para o usuário que representa (dessa forma, nossa saída será mais agradável) nos dados que ele continua transmitindo para si próprio. O que mais queremos manter na memória No nosso caso, queremos os outros pid, os itens que oferecemos e os itens que o outro oferece. Também iria adicionar a referência de um monitor (então sabemos abortar se o outro morre) e um de campo, costumava fazer respostas atrasadas: no caso de init1. Bem, apenas se preocupa com nosso nome por agora. Tenha em atenção que bem, comece no estado ocioso: as próximas devoluções a serem consideradas seriam os próprios estados. Até agora descrevi as transições de estado e as chamadas que podem ser feitas, mas é preciso uma maneira de garantir que tudo esteja bem. Bem, escreva algumas funções de utilidade primeiro: e podemos começar com o estado ocioso. Por causa da convenção, Ill aborda a versão assíncrona primeiro. Este não deve cuidar de nada, exceto o outro jogador que pede uma troca dada pelo nosso próprio jogador, se você olhar para as funções da API, usará uma chamada síncrona: um monitor está configurado para nos permitir lidar com o outro morrendo, e Sua referência é armazenada nos dados dos FSM junto com os outros pid, antes de mudar para o estado de idlewait. Observe que iremos denunciar todos os eventos inesperados e ignorá-los ao permanecer no estado em que já estávamos. Podemos ter algumas mensagens fora da banda aqui e ali que poderiam ser o resultado de condições de corrida. É geralmente seguro ignorá-los, mas não podemos facilmente livrar-se deles. É melhor não bater todo o FSM nestas mensagens desconhecidas, mas algo esperadas. Quando nosso próprio cliente solicita ao FSM que entre em contato com outro jogador para uma troca, ele enviará um evento síncrono. O retorno de chamada idle3 será necessário: procedemos de forma semelhante à versão assíncrona, exceto que precisamos realmente perguntar ao outro lado se querem negociar conosco ou não. Você notará que ainda não respondemos ao cliente. Isso porque não temos nada de interessante para dizer, e queremos que o cliente fique trancado e aguarde que o comércio seja aceito antes de fazer qualquer coisa. A resposta só será enviada se o outro lado aceitar uma vez estiver em idlewait. Quando estivemos lá, temos que lidar com a outra aceitar negociar e a outra pedindo para negociar (o resultado de uma condição de corrida, conforme descrito no protocolo): Isso nos dá duas transições para o estado de negociação, mas lembre-se de que devemos Use genfsm: responda2 responda ao nosso cliente para dizer que está certo começar a oferecer itens. Há também o caso do nosso cliente de FSM aceitando o comércio sugerido pela outra parte: Novamente, este avança para o estado de negociação. Aqui, devemos lidar com consultas assíncronas para adicionar e remover itens provenientes do cliente e do outro FSM. No entanto, ainda não decidimos como armazenar itens. Porque eu sou um pouco preguiçoso e eu suponho que os usuários não trocam esses itens, listas simples vão fazê-lo por enquanto. No entanto, podemos mudar de idéia em um ponto posterior, por isso seria uma boa idéia para embrulhar operações de itens em suas próprias funções. Adicione as seguintes funções na parte inferior do arquivo com aviso3 e inesperado2: Simples, mas eles têm o papel de isolar as ações (adicionar e remover itens) de sua implementação (usando listas). Poderíamos facilmente mudar para proplistas, arrays ou qualquer outra estrutura de dados sem interromper o resto do código. Usando ambas as funções, podemos implementar a oferta e a remoção de itens: é um aspecto feio de usar mensagens assíncronas em ambos os lados. Um conjunto de mensagens tem a forma de fazer e retrair, enquanto o outro faz e desfaz. Isso é inteiramente arbitrário e apenas usado para diferenciar entre comunicações de jogadores-para-FSM e comunicações FSM-para-FSM. Note-se que, naqueles que vêm de nosso próprio jogador, temos que dizer ao outro lado sobre as mudanças que estavam fazendo. Outra responsabilidade é lidar com a mensagem que você mencionou no protocolo. Este é o último evento assíncrono a manipular no estado de negociação: conforme descrito no protocolo, sempre que não estavam no estado de espera e recebem esta mensagem, devemos responder com o notyet. Também estavam produzindo detalhes do comércio para o usuário, para que uma decisão possa ser tomada. Quando essa decisão for tomada e o usuário estiver pronto, o evento pronto será enviado. Este deve ser síncrono porque não queremos que o usuário continue modificando sua oferta adicionando itens enquanto reivindica estar pronto: neste momento, uma transição para o estado de espera deve ser feita. Note que apenas esperar pelo outro não é interessante. Nós salvamos a variável From para que possamos usá-la com genfsm: reply2 quando tivermos algo a dizer ao cliente. O estado de espera é uma fera engraçada. Novos itens podem ser oferecidos e retraídos porque o outro usuário pode não estar pronto. Faz sentido, então, reverter automaticamente para o estado de negociação. Seria bom para oferecer excelentes itens para nós, apenas para o outro para removê-los e declarar-se pronto, roubando nosso pilhagem. Voltando à negociação é uma boa decisão: agora é algo significativo e respondemos ao jogador com as coordenadas que armazenamos no Sstate. from. O próximo conjunto de mensagens com as quais precisamos nos preocupar são aqueles relacionados com a sincronização de ambos os EFM para que eles possam mudar para o estado pronto e confirmar o comércio. Para este, devemos realmente nos concentrar no protocolo definido anteriormente. As três mensagens que podemos ter são você já (porque o outro usuário se declarou pronto), não porque (porque pedimos ao outro se ele estava pronto e não estava) e pronto (porque pedimos ao outro se ele estivesse pronto e ele estava ). Bem, comece com você já. Lembre-se que, no protocolo, dissemos que poderia haver uma condição de corrida escondida. A única coisa que podemos fazer é enviar a mensagem pronta com amready1 e lidar com o resto mais tarde: Bem, ficar preso esperando novamente, então não vale a pena responder ao nosso cliente ainda. Da mesma forma, não responderemos ao cliente quando o outro lado enviar um convite para o nosso convite: por outro lado, se o outro estiver pronto, enviamos uma mensagem extra pronta ao outro FSM, responda ao nosso próprio usuário e, em seguida, vá para O estado pronto: você pode ter percebido que eu usei acktrans1. Na verdade, ambos os FSM devem usá-lo. Por que isso? Para entender isso, temos que começar a olhar para o que está acontecendo no estado pronto. Quando estiver pronto, ambas as ações dos jogadores tornam-se inúteis (exceto o cancelamento). Não nos importaremos com ofertas de novos itens. Isso nos dá alguma liberdade. Basicamente, ambos os FSMs podem conversar livremente uns aos outros sem se preocupar com o resto do mundo. Isso nos permite implementar nossa batardização de um commit de duas fases. Para começar este commit sem qualquer atuação do jogador, é preciso um evento para desencadear uma ação dos FSMs. O evento ack do acktrans1 é usado para isso. Assim que estava no estado pronto, a mensagem é tratada e atuada quando a transação pode começar. Os compromissos de duas fases exigem comunicações síncronas, no entanto. Isso significa que não podemos ter ambos os FSMs iniciando a transação de uma só vez, porque acabarão em impasse. O segredo é encontrar uma maneira de decidir que uma máquina de estado finito deve iniciar a confirmação, enquanto a outra se sentará e aguardará pedidos do primeiro. Acontece que os engenheiros e cientistas da computação que desenharam Erlang eram bastante inteligentes (bem, já sabíamos disso). As pêndidas de qualquer processo podem ser comparadas entre si e classificadas. Isso pode ser feito, não importa quando o processo foi gerado, seja ainda vivo ou não, ou se ele vem de outra VM (veja mais sobre isso quando entramos em Erlang distribuído). Sabendo que dois pids podem ser comparados e um será maior do que o outro, podemos escrever uma função prioridade2 que levará duas pids e diga um processo, seja eleito ou não: E ao chamar essa função, podemos começar um processo O compromisso eo outro seguindo as ordens. É o que isso nos dá quando incluído no estado pronto, depois de receber a mensagem ack: Esta grande tentativa. A expressão catch é o FSM líder que decide como o commit funciona. Tanto askcommit1 como docommit1 são síncronos. Isso permite que o FSM líder os ligue livremente. Você pode ver que o outro FSM simplesmente vai e espera. Em seguida, receberá as ordens do processo principal. A primeira mensagem deve ser askcommit. Isso é apenas para se certificar de que ambos os FSM ainda estão lá, nada aconteceu, eles estão ambos dedicados a completar a tarefa: uma vez que isso seja recebido, o processo líder exigirá a confirmação da transação com docommit. Isso é quando devemos comprometer nossos dados: e uma vez feito, nós deixamos. O FSM líder receberá ok como uma resposta e saberá se comprometer em seu próprio fim depois. Isso explica por que precisamos da grande tentativa. pegar. Se o FSM de resposta morrer ou o jogador cancelar a transação, as chamadas síncrona falharão após um tempo limite. O compromisso deve ser abortado neste caso. Apenas para saber, eu defini a função de confirmação da seguinte forma: Muito desanimador, eh. Na geral, não é possível fazer um verdadeiro compromisso seguro com apenas dois participantes. O terceiro é geralmente obrigado a julgar se ambos os jogadores fizeram tudo certo. Se você escrevesse uma verdadeira função de confirmação, ele deve entrar em contato com esse terceiro em nome de ambos os jogadores e, em seguida, fazer a gravação segura em um banco de dados para eles ou reverter toda a troca. Nós não entraremos em tais detalhes e a função commit1 atual será suficiente para as necessidades deste livro. Ainda não foram concluídos. Nós ainda não abordamos dois tipos de eventos: um jogador que cancelou o comércio e os outros jogadores finalizam a máquina de estado. O primeiro pode ser tratado usando o callbacks handleevent3 e handlesyncevent4. Sempre que o outro usuário cancelar, bem, receba uma notificação assíncrona: quando o fazemos, não devemos esquecer de dizer ao outro antes de nos desistir: e voil O último evento a cuidar é quando o outro FSM desce. Felizmente, configuramos um monitor no estado ocioso. Podemos combinar com isso e reagir em conformidade: Note que, mesmo que os eventos de cancelamento ou ABAIXO aconteçam enquanto estavam no commit, tudo deve ser seguro e ninguém deve obter seus itens roubados. Nota: usamos io: format2 para a maioria de nossas mensagens para permitir que os FSMs se comuniquem com seus próprios clientes. Em um aplicativo do mundo real, podemos querer algo mais flexível do que isso. One way to do it is to let the client send in a Pid, which will receive the notices sent to it. That process could be linked to a GUI or any other system to make the player aware of the events. The io:format2 solution was chosen for its simplicity: we want to focus on the FSM and the asynchronous protocols, not the rest. Only two callbacks left to cover Theyre codechange4 and terminate3. For now, we dont have anything to do with codechange4 and only export it so the next version of the FSM can call it when itll be reloaded. Our terminate function is also really short because we didnt handle real resources in this example: We can now try it. Well, trying it is a bit annoying because we need two processes to communicate to each other. To solve this, Ive written the tests in the file tradecalls. erl. which can run 3 different scenarios. The first one is mainab0. It will run a standard trade and output everything. The second one is maincd0 and will cancel the transaction halfway through. The last one is mainef0 and is very similar to mainab0. except it contains a different race condition. The first and third tests should succeed, while the second one should fail (with a crapload of error messages, but thats how it goes). You can try it if you feel like it. If youve found this chapter a bit harder than the others, I must remind you that its entirely normal. Ive just gone crazy and decided to make something hard out of the generic finite-state machine behaviour. If you feel confused, ask yourself these questions: Can you understand how different events are handled depending on the state your process is in Do you understand how you can transition from one state to the other Do you know when to use sendevent2 and syncsendevent2-3 as opposed to sendallstateevent2 and syncsendallstateevent3. If you answered yes to these questions, you understand what genfsm is about. The rest of it with the asynchronous protocols, delaying replies and carrying the From variable, giving a priority to processes for synchronous calls, bastardized two-phase commits and whatnot are not essential to understand . Theyre mostly there to show what can be done and to highlight the difficulty of writing truly concurrent software, even in a language like Erlang. Erlang doesnt excuse you from planning or thinking, and Erlang wont solve your problems for you. Itll only give you tools. That being said, if you understood everything about these points, you can be proud of yourself (especially if you had never written concurrent software before). You are now starting to really think concurrently. In a real game, there is a lot more stuff going on that could make trading even more complex. Items could be worn by the characters and damaged by enemies while theyre being traded. Maybe items could be moved in and out of the inventory while being exchanged. Are the players on the same server If not, how do you synchronise commits to different databases Our trade system is sane when detached from the reality of any game. Before trying to fit it in a game (if you dare), make sure everything goes right. Test it, test it, and test it again. Youll likely find that testing concurrent and parallel code is a complete pain. Youll lose hair, friends and a piece of your sanity. Even after this, youll have to know your system is always as strong as its weakest link and thus potentially very fragile nonetheless. Dont Drink Too Much Kool-Aid: While the model for this trade system seems sound, subtle concurrency bugs and race conditions can often rear their ugly heads a long time after they were written, and even if theyve been running for years. While my code is generally bullet proof (yeah, right), you sometimes have to face swords and knives. Beware the dormant bugs. Fortunately, we can put all of this madness behind us. Well next see how OTP allows you to handle various events, such as alarms and logs, with the help of the genevent behaviour. Except where otherwise noted, content on this site is licensed under a Creative Commons Attribution Non-Commercial No Derivative License

No comments:

Post a Comment