Friday 26 April 2019

Sistema comercial erlang


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-solutionsresources. 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 Hey, parece que o Javascript está desativado. Isso é bom, o site funciona sem ele. No entanto, você pode preferir lê-lo com o destaque da sintaxe, o que requer a ração de javascript contra as máquinas de estado finito. 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: sentando, atira ou meneando 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 é realmente o núcleo dos FSMs implementados como processos Erlang. Há coisas que poderiam ter sido feitas de forma diferente: poderíamos ter estado passado 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 finalização, atualizações de código manipuladas, 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 representação mais simples da pura preguiça inexplorada. Existem outras formas de eventos 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 do mesmo. 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. Mais uma vez, continue com as devoluções 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 os servidores gs, e hibernar e Timeout mantêm a mesma semântica. O que é novo aqui é a 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 marcador de posição 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 em que 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ê está morto no estado, então estou surpreso por você mesmo ler esse texto. De volta ao nosso FSM. A função init1 disse que devemos estar sentados. Sempre que o processo genfsm recebe um evento, a função sitting2 ou sitting3 será chamada. A função sitting2 é chamada para eventos assíncronos e sitting3 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 maneira 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 se encontravam (o alimento que cheirava o cão derrubará o que quer que esteja fazendo e, em vez disso, procurará comida). 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 estado do evento quando o evento foi recebido. Ele retorna os mesmos valores que StateName2. Handlesyncevent O backlink 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 s, 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 contrário 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 interruptores de telefone para máquinas de estado. Devido a 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 onde ambos os jogadores se falassem diretamente (o que teria a vantagem de ser distribuível). Porque a implementação é complicada, vou gastar um bom tempo ao descrevê-lo, o tipo de problemas a enfrentar e as formas de corrigi-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 esteja 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, portanto, 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, a Carls FSM deve ser informada 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 perceber 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 aguarda sua resposta, ao mesmo tempo em que o Carls FSM envia uma mensagem ao 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 então haverá sobras de mensagens 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 ficar 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, toda a troca pode parecer um pouco assim: ambos os FSMs estão no estado ocioso. Quando você pede que o Jim troque, Jim tem que aceitar antes que as coisas mudem. Então, ambos podem oferecer itens ou retirá-los. Quando você está se declarando pronto, 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 nisso, 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. No início, ambas as máquinas de estado finito 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 parecendo assim: então, isso é praticamente o oposto, como os dois diagramas de estados anteriores agrupados em um. Note que esperamos que o jogador aceite a oferta neste caso. O que acontece se, por pura sorte, pedimos ao outro jogador que troque conosco ao mesmo tempo que ele nos pede para negociar. O que acontece aqui é que ambos os clientes perguntam ao seu próprio FSM para negociar com o outro. Assim que as mensagens de negociação for enviada, ambos os FSMs mudam para o estado de idlewait. Então eles serão capazes de 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 bem, receba pergunte negociar mensagens enquanto estiver no estado de idlewait. Conseqüentemente, 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 os dois 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 depois retrair a oferta: Tudo isso faz é encaminhar a mensagem de nossos clientes para o outro FSM. Ambas as 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 pensarmos ser bastante generoso, temos que dizer que estávamos prontos para oficializar o comércio. Porque temos que sincronizar ambos os jogadores, devemos usar um estado intermediário, como fizemos para ocioso e idlewait: o que fazemos aqui é que assim que o nosso jogador estiver pronto, nosso FSM pede Jims FSM se estiver pronto. Enquanto aguarda a resposta, nosso próprio FSM cai em seu estado de espera. A resposta bem obtida dependerá do estado do Jims FSM: se estiver em espera, isso vai nos dizer que está pronto. Caso contrário, nos diga que ainda não está pronto. Isso é precisamente o que nosso FSM responde automaticamente a Jim se ele nos perguntar se estamos prontos quando estamos 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 tão prontos, bem, perguntei a Jim se ele também estava pronto e seu FSM ainda não respondeu: Não está pronto, mas nós somos. Não podemos fazer muito, mas continuar esperando. Enquanto espera 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 Im pronto, Estragando-nos no processo. Assim que ele muda os itens oferecidos, voltamos para o estado de negociação para que possamos modificar nossa própria oferta, ou examinar o atual e decidir estarem prontos. Enxague e repita. Em algum momento, Jim estará pronto para finalizar o comércio também. Quando isso acontecer, sua máquina de estados finitos perguntará a nossa se estivermos 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 expliquei. Por causa da forma como as mensagens são recebidas, possamos apenas processar a oferta do item depois de nos declararmos prontos 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ão saberíamos o que fazer. Isso também pode 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. Eu coloquei no diagrama por razões de correção, 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 uma confirmação em duas fases para garantir que as coisas funcionem bem ao fazer o comércio oficial: nossa versão (como descrito acima) será bastante simplista. Escrever um compromisso de duas fases verdadeiramente correto exigiria muito mais código do que o que é 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 deixar o outro lado saber 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 ter que lê-lo mais de uma vez, especialmente se você não está acostumado a protocolos assíncronos. Se for esse o caso, eu o encorajo a tentar projetar seu próprio protocolo. Então, pergunte-se o que acontece se duas pessoas fizerem as mesmas ações de forma muito rápida. O que acontece se 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 (deixe-me saber se este for 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 do genfsm. Isso ocorre porque o outro FSM também será executado no módulo tradefsm e pode acessá-los de dentro: então, essa é a nossa API. Você pode ver Im planejando ter algumas funções tanto síncronas quanto 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 se juntar 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 lidar com a confirmação 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 eo diagrama de diagrama de seqüência de dados anteriores devem ser suficientes. No entanto, você ainda pode transcrevê-los para a 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 mantenha um nome para o usuário que representa (dessa forma, nossa saída será mais agradável) nos dados que 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, saberemos abortar se o outro morre) e um de campo, costumavam fazer respostas atrasadas: no caso de init1. Bem, apenas se preocupa com nosso nome por agora. Tenha em atenção que, bem, começam no estado ocioso: as próximas devoluções a considerar 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 uma questão de convenção, eu abordo a versão assíncrona primeiro. Este não precisa se preocupar com nada, exceto o outro jogador que pede uma troca com o 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. Tenha em atenção que informaremos 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 um comércio, 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 preso 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 estava 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, esse se dirige para o estado de negociação. Aqui, devemos lidar com consultas assíncronas para adicionar e remover itens provenientes tanto do cliente quanto 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 troquem esses itens, listas simples irã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 de 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 já mencionada que mencionamos 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. Foram também a saída de 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 o 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 besta 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 nossa pilhagem. Voltando à negociação é uma boa decisão: agora é algo significativo e nós 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 a nosso cliente ainda. Da mesma forma, não responderemos ao cliente quando o outro lado envia um convite para o nosso convite: por outro lado, se o outro estiver pronto, enviamos uma mensagem extra pronta para o outro FSM, responda ao nosso próprio usuário e depois mude para O estado pronto: você pode ter notado que eu usei o Acktrans1. Na verdade, ambos os FSMs 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, as duas 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 bastardização de um commit em duas fases. Para começar este commit sem que nenhum dos jogadores atue, é preciso um evento para desencadear uma ação dos FSMs. O evento ack do acktrans1 é usado para isso. Assim que estiveram 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á ordens do primeiro. Acontece que os engenheiros e cientistas de computação que projetaram Erlang eram bastante inteligentes (bem, já sabíamos disso). Os pids de qualquer processo podem ser comparados entre si e classificados. 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 iniciar um processo O commit e o outro seguindo as ordens. Heres 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. Ambos askcommit1 e 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. Ele 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 pedirá para confirmar a transação com docommit. Isso é quando devemos comprometer nossos dados: e uma vez que é 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. Para que você saiba, eu defini a função de confirmação da seguinte forma: Muito desalentador, 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, 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 cancela, 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 de acordo: Note que, mesmo que os eventos de cancelamento ou BAIXO aconteçam enquanto estavam no commit, tudo deve ser seguro e ninguém deve tirar 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 uma aplicação 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 LicenseTrading Systems Expert Kenny Stone develops front and back office trading systems for Connamara Systems, LLC, with a prior background in C and assembly for embedded telecommunications products. His experience at Connamara has been varied, building low latency CC trading applications as well as web applications with Ruby on Rails and Sinatra. As a senior engineer at Connamara, Kenny is tasked with pairing the right technology for the problem at hand, and he believes that Erlang offers a compelling choice for many solutions in financial software engineering. Connamara Systems provides end-to-end trading systems development including order and execution management, algorithmic trading, exchange connectivity and market data integration. Our clients are top-tier futures, options and equities trading companies. Kenny Stone is Giving the Following Talks The Erlang Stock Exchange One year ago, Joel Reymont challenged the Erlang community to build an open source stock market. In this talk, youll see the result of my effort to build it - the triumphs, the frustrations, and the benchmarks and I will directly compare the Erlang solution to a Java solution we built for a client.

No comments:

Post a Comment