pypoker icon indicating copy to clipboard operation
pypoker copied to clipboard

[Feature request] Avoid H15 errors by keeping connection open

Open connorourke opened this issue 4 years ago • 7 comments

I am keen to use this nice web-app for a Christmas game with friends, but would also like to remove the bet timeouts, so that people aren't rushed.

I've extended the timeout, but now get h15 errors:

at=error code=H15 desc="Idle connection" method=GET path="/poker/texas-holdem"

due to the heroku 55 second timeout window ( see https://devcenter.heroku.com/articles/http-routing#timeouts)

Would it be possible to include a feature to ping the connection and keep it open if the bet-timeout is extended beyond the 55 heroku timeout limit?

connorourke avatar Dec 17 '20 06:12 connorourke

if I remember correctly there's already a ping kind of message available, so I'd imagine this wouldn't be too hard to do. it'd be nice if timeouts were somehow configurable via an environment variable. I don't have time to work on this unfortunately, but feel free to send a PR

epifab avatar Dec 17 '20 10:12 epifab

Thanks for this @epifab

I had a look through and found the ping message already in there, and had a go at implementing something to prevent the timeout. I'm new to web-apps, and have never played about with them until now. This is a bit hacky, but I wanted to see if it works.

I wrapped the bet_round in poker_game.py, and included a call to ping each of the players every 40 seconds:

def bet_round(self, dealer_id: str, bets: Dict[str, float], get_bet_function, on_bet_function=None) -> Optional[PlayerServer]:


     round_queue = Queue()
     got_best = threading.Event()
     on_bet_function = None
     round_thread = threading.Thread(target=lambda q, args: q.put(self.bet_round_wrapped(*args)), args=[round_queue,[dealer_id, bets, get_bet_function, got_best, on_bet_function]])
     round_thread.start()

     while not got_best.wait(timeout=40):
         players = list(self._game_players.round(dealer_id))
         print("list of players",players,type(players[0]))
         if players:
             player_list = list(self._game_players.round(dealer_id))
             for player in player_list:
                 print("in bet_round - gonna ping player",player)
                 if not player.ping():
                     print("Unable to ping, gonna remove player")
                     self._game_players.remove(player.id)
                 else:
                     print("successfully pinged player")
         else:
             print("all out of players")
             got_best.set()

     print("Left the loop here")
     round_thread.join()
     best_player = round_thread.join()
     return best_player

 def bet_round_wrapped(self, dealer_id: str, bets: Dict[str, float], get_bet_function, got_best, on_bet_function=None) -> Optional[PlayerServer]:
     """
     performs a complete bet round
     returns the player who last raised - if nobody raised, then the first one to check
     """
     players_round = list(self._game_players.round(dealer_id))

     if len(players_round) == 0:
         raise GameError("No active players in this game")

     # The dealer might be inactive. Moving to the first active player
     dealer = players_round[0]

     for k, player in enumerate(players_round):
         if player.id not in bets:
             bets[player.id] = 0
         if bets[player.id] < 0 or (k > 0 and bets[player.id] < bets[players_round[k - 1].id]):
             # Ensuring the bets dictionary makes sense
             raise ValueError("Invalid bets dictionary")

     best_player = None

     while dealer is not None and dealer != best_player:
         next_player = self._game_players.get_next(dealer.id)

         max_bet = self._get_max_bet(dealer, bets)
         min_bet = self._get_min_bet(dealer, bets)

         if max_bet == 0.0:
             # No bet required to this player (either he is all-in or all other players are all-in)
             bet = 0.0
         else:
             # This player isn't all in, and there's at least one other player who is not all-in
             bet = get_bet_function(player=dealer, min_bet=min_bet, max_bet=max_bet, bets=bets)

         if bet is None:
             self._game_players.remove(dealer.id)
         elif bet == -1:
             self._game_players.fold(dealer.id)
         else:
             if bet < min_bet or bet > max_bet:
                 raise ValueError("Invalid bet")
             dealer.take_money(bet)
             bets[dealer.id] += bet
             if best_player is None or bet > min_bet:
                 best_player = dealer

         if on_bet_function:
             on_bet_function(dealer, bet, min_bet, max_bet, bets)

         dealer = next_player

     got_best.set()
     return best_player

It looks like it works for a few calls of the ping, but then fails. I get the following in the logger:


2020-12-18T16:27:33.156525+00:00 app[web.1]: [2020-12-18 16:27:33,156] INFO in player_client: player 1853f194-bdf3-4abd-9743-47a1757a6bd8: connected to server 18bb7be6-e48e-4175-b221-b84b25bb6dee
2020-12-18T16:27:33.621393+00:00 heroku[router]: at=info method=GET path="/static/images/cards-medium.png" host=bourkesgarage.herokuapp.com request_id=10b78584-89f6-4a8f-8b49-7ace453ed43c fwd="81.170.56.125" dyno=web.1 connect=0ms service=92ms status=200 bytes=38539 protocol=http
2020-12-18T16:27:33.448527+00:00 heroku[router]: at=info method=GET path="/static/images/chips-small.png" host=bourkesgarage.herokuapp.com request_id=1264b65b-e84e-4f8b-91d4-ac77a893a8d6 fwd="81.170.56.125" dyno=web.1 connect=1ms service=11ms status=200 bytes=2368 protocol=http
2020-12-18T16:27:33.609812+00:00 heroku[router]: at=info method=GET path="/static/images/chips-50.png" host=bourkesgarage.herokuapp.com request_id=a112fed6-b7a1-4d9e-bf25-2ead196e0b03 fwd="81.170.56.125" dyno=web.1 connect=0ms service=76ms status=200 bytes=4834 protocol=http
2020-12-18T16:27:33.625784+00:00 heroku[router]: at=info method=GET path="/static/images/cards-small.png" host=bourkesgarage.herokuapp.com request_id=17cb122c-49c1-4035-a1f1-663a5d930cff fwd="81.170.56.125" dyno=web.1 connect=29ms service=137ms status=200 bytes=16891 protocol=http
2020-12-18T16:27:44.432880+00:00 app[worker.1]: Left the loop here
2020-12-18T16:27:50.951498+00:00 heroku[router]: at=info method=GET path="/static/images/cards-large.png" host=bourkesgarage.herokuapp.com request_id=2dc4482e-3b59-4c38-8048-831ca877b36e fwd="81.170.56.125" dyno=web.1 connect=14ms service=57ms status=200 bytes=77043 protocol=http
2020-12-18T16:28:31.735339+00:00 app[worker.1]: list of players [<poker.player_server.PlayerServer object at 0x7f6af3672820>, <poker.player_server.PlayerServer object at 0x7f6af368d610>, <poker.player_server.PlayerServer object at 0x7f6af3672370>] <class 'poker.player_server.PlayerServer'>
2020-12-18T16:28:31.735403+00:00 app[worker.1]: in bet_round - gonna ping player player 0e622560-cd2a-4b40-8847-ff4188c10893
2020-12-18T16:28:31.916022+00:00 app[worker.1]: successfully pinged player
2020-12-18T16:28:31.916052+00:00 app[worker.1]: in bet_round - gonna ping player player 1853f194-bdf3-4abd-9743-47a1757a6bd8
2020-12-18T16:28:32.137961+00:00 app[worker.1]: successfully pinged player
2020-12-18T16:28:32.137986+00:00 app[worker.1]: in bet_round - gonna ping player player c49273b8-1cc7-4794-8f8e-875aa6e8b01a
2020-12-18T16:28:32.258569+00:00 app[worker.1]: successfully pinged player
2020-12-18T16:29:12.258811+00:00 app[worker.1]: list of players [<poker.player_server.PlayerServer object at 0x7f6af3672820>, <poker.player_server.PlayerServer object at 0x7f6af368d610>, <poker.player_server.PlayerServer object at 0x7f6af3672370>] <class 'poker.player_server.PlayerServer'>
2020-12-18T16:29:12.258845+00:00 app[worker.1]: in bet_round - gonna ping player player 0e622560-cd2a-4b40-8847-ff4188c10893
2020-12-18T16:29:12.470429+00:00 app[worker.1]: successfully pinged player
2020-12-18T16:29:12.470437+00:00 app[worker.1]: in bet_round - gonna ping player player 1853f194-bdf3-4abd-9743-47a1757a6bd8
2020-12-18T16:29:12.576479+00:00 app[worker.1]: successfully pinged player
2020-12-18T16:29:12.576555+00:00 app[worker.1]: in bet_round - gonna ping player player c49273b8-1cc7-4794-8f8e-875aa6e8b01a
2020-12-18T16:29:12.684640+00:00 app[worker.1]: successfully pinged player
2020-12-18T16:29:52.684880+00:00 app[worker.1]: list of players [<poker.player_server.PlayerServer object at 0x7f6af3672820>, <poker.player_server.PlayerServer object at 0x7f6af368d610>, <poker.player_server.PlayerServer object at 0x7f6af3672370>] <class 'poker.player_server.PlayerServer'>
2020-12-18T16:29:52.684925+00:00 app[worker.1]: in bet_round - gonna ping player player 0e622560-cd2a-4b40-8847-ff4188c10893
2020-12-18T16:29:52.816735+00:00 app[worker.1]: successfully pinged player
2020-12-18T16:29:52.816744+00:00 app[worker.1]: in bet_round - gonna ping player player 1853f194-bdf3-4abd-9743-47a1757a6bd8
2020-12-18T16:29:53.038439+00:00 app[worker.1]: successfully pinged player
2020-12-18T16:29:53.038495+00:00 app[worker.1]: in bet_round - gonna ping player player c49273b8-1cc7-4794-8f8e-875aa6e8b01a
2020-12-18T16:29:53.226873+00:00 app[worker.1]: successfully pinged player
2020-12-18T16:30:33.227221+00:00 app[worker.1]: list of players [<poker.player_server.PlayerServer object at 0x7f6af3672820>, <poker.player_server.PlayerServer object at 0x7f6af368d610>, <poker.player_server.PlayerServer object at 0x7f6af3672370>] <class 'poker.player_server.PlayerServer'>
2020-12-18T16:30:33.227384+00:00 app[worker.1]: in bet_round - gonna ping player player 0e622560-cd2a-4b40-8847-ff4188c10893
2020-12-18T16:30:33.366195+00:00 app[worker.1]: successfully pinged player
2020-12-18T16:30:33.366305+00:00 app[worker.1]: in bet_round - gonna ping player player 1853f194-bdf3-4abd-9743-47a1757a6bd8
2020-12-18T16:30:35.374865+00:00 app[worker.1]: ERROR:root:Unable to ping player 1853f194-bdf3-4abd-9743-47a1757a6bd8: Timed out
2020-12-18T16:30:35.377289+00:00 app[worker.1]: Unable to ping, gonna remove player
2020-12-18T16:30:35.377432+00:00 app[worker.1]: in bet_round - gonna ping player player c49273b8-1cc7-4794-8f8e-875aa6e8b01a
2020-12-18T16:30:35.514655+00:00 app[worker.1]: successfully pinged player
2020-12-18T16:30:35.382919+00:00 app[web.1]: [2020-12-18 16:30:35,382] INFO in client_web: player 1853f194-bdf3-4abd-9743-47a1757a6bd8 connection closed

With this error towards the bottom.

2020-12-18T16:30:35.374865+00:00 app[worker.1]: ERROR:root:Unable to ping player 1853f194-bdf3-4abd-9743-47a1757a6bd8: Timed out

I don't suppose at a glance you can tell what I'm doing wrong, or if this is entirely the wrong way top go about it all together?

Thanks!

connorourke avatar Dec 18 '20 18:12 connorourke

Ah - I have found that there is at least one reason why this approach won't work. The player can bet while the server is expecting a pong message, and it'll return an error. If you spot any other issues feel free to point them out.

connorourke avatar Dec 19 '20 06:12 connorourke

Hi @epifab - I have this sorted. I'll send a PR once I get a chance to play about with it a bit more & tidy it up.

connorourke avatar Dec 21 '20 13:12 connorourke

hi there, sorry I couldn't help much here, but that sounds great. looking forward to it

epifab avatar Dec 21 '20 13:12 epifab

hi @connorourke, I just realised that it might be a lot easier if a ping message was either sent by the JS client or the webapp directly, without having to hook this up to the main game logic

epifab avatar Jan 05 '21 13:01 epifab

Hi @epifab - sorry, I'm back to work, so havent had time to look at this. The fix I tested myself unceremoniously fell over when I got other people together for a game. Not sure what the problem is.

Not having to hook this up to the main game logic certainly sounds simpler - I'll have a think about doing it that way when I get the time.

connorourke avatar Jan 19 '21 10:01 connorourke