jagordon59 has asked for the wisdom of the Perl Monks concerning the following question:

I have an online game with the game server written in Perl 5.32 hosted on Amazon Linux where players connect from their browser using web sockets. In addition to real players connecting, Bot players can also connect which is via a Perl script on the same server where the game server is running. The game works fine but of course you have to consider cases where a user wants to explicitly quit the game, closes their browser (or tab) or refreshes their browser tab. To deal with these I set an alarm for 1 second whenever getting a disconnect. If a player was just refreshing their browser, they will reconnect immediately cancelling the alarm and all is good. If they were explicitly quitting or closed their browser tab, there will be no reconnect and the alarm should go off and the sub will remove them from the game. This all works fine when it's just real players in a game all connected from their browser. However, when there is one or more bots connected and one of the real players disconnects, the alarm is set but NEVER goes off.

The failure occurs the next time the game server has to send a message out to all connected players. Since the alarm didn't go off, that disconnected player didn't get removed from the list. And when time the game server tries to send a message to that player, it pukes due to a syswrite error on the socket that was internally closed.

I have tried using unsafe signals and POSIX signal handler but neither of these work any differently or better.

$conn->on( disconnect => sub { my $disconnectPlayer = $self->{players}->getPlayerWithSocket( +$conn ); $SIG{ALRM} = sub { $self->playerDisconnected() }; $self->{playerDisconnected} = 1; $self->{disconnectedPlayerSocket} = $conn; alarm( 1 ); } );

So the bottom line is, why would the alarm be affected by having an incoming web socket connection from a process on the local host?

Replies are listed 'Best First'.
Re: Perl alarm not going off when local websocket connection is active
by Fletch (Bishop) on Apr 27, 2025 at 12:31 UTC

    Just an early pre-caffeine hunch but if I'm reading your description correctly what's prossibly happening is that you've got multiple connections all calling this code and diddling the $SIG{ALRM} setting to heir own (different) coderef which winds up clobbering the others. There's only one signal handler per signal per process / signal handling thread (threading making things much more interesting maybe) on *NIX. Possible scenario might be if you have more than one connection and you happen to get two disconnections in a row; the first disconnect to fire (say for Bob) sets up the handler, but before it runs a disconnect for Egon comes in and changes the ALRM handler to call for him and Bob's not your uncle (he's instead a zombie connection in your player hash because the handler never ran to clear him out).

    You don't specify what module or framework but usually things like this are written using some sort of event loop which is supposed to be the thing handling signals and timeouts and what not. Rather than you directly handling the $SIG{ALRM} yourself those will usually provide a mechanism to register callbacks and timeouts that will be triggered by the framework in a way that won't clobber other settings. My description's a bit handwavy and general but if you provide a bit more details (what web framework is in use) I bet someone will be able to give concrete examples.

    The cake is a lie.
    The cake is a lie.
    The cake is a lie.

      Cyclic callbacks might also be an option. My guess is those are already implemented in some way to handle the cyclic nature of the game logic (if it's one of those real time games). So setting a player state for "is in timeout since..." and checking that in the cyclic callback could also be an option.

      Additionally, in my own software, i send regular messages (pings) in both directions. This keeps the connection open (especially on NATed networks), and also provides the functionality to check for application timeouts. (Just because a connection is open doesn't mean the application at the other end still functions correctly).

      PerlMonks XP is useless? Not anymore: XPD - Do more with your PerlMonks XP
      Also check out my sisters artwork and my weekly webcomics
      Thanks for your insight. I'll check to see if that is possibly the case and post more about the framework here if I don't uncover anything on that.
        Well what framework is it? Does it offer its own timer function?
Re: Perl alarm not going off when local websocket connection is active
by NERDVANA (Priest) on Apr 30, 2025 at 04:14 UTC
    As fletch mentioned, there is only one alarm per process. (the alarm documentation also warns that some systems may implement sleep() using alarm, but that's generally not the case on Linux) Overall, alarm makes a poor tool for anything but the most simple low-level interruptions of a blocking syscall like recv() or accept().

    If you wrote it with websockets, I would assume you are using some sort of event library? If so, use a timer from your event library. If you are not using an event library, you should let us know a little more about the structure of the program, but we'll probably recommend something like using a select() timeout on your main loop and then checking a global list of timestamped things to see if one of them needs dealt with.

    Another warning based on that snippet you shared, perl does not have convenient garbage collection like javascript; it uses reference counting and you need to ensure that you don't create reference loops. You have to be careful which variables you use inside callbacks. In that example, it appears that you have $conn referencing a disconnect callback which closes over the variable $conn, so conn refers to a coderef and the coderef refers to conn, forming a loop. Likewise, $self refers to $conn and $conn refers to a coderef and the coderef refers to $self. This means $conn (and $self) will never get garbage collected unless you do one of:

    • Break all loops when you know its time to destroy the connection, by removing all the listener callbacks somehow
    • Use Scalar::Util::weaken on $self and $conn variables used in the callback (but make sure to keep one strong reference somewhere)
    • Avoid using the $self or $conn variables from the outer scope in your callback, using instead the copies of those references that get passed as parameters to the callback.

    In short, for automatic garbage collection in Perl you need to imagine a tree of ownership of objects in your program, and any time you want an owned object to refer back to its owner, that needs to be a weak reference.

    I've found that Mojo::IOLoop has the nicest object API to help you declare trees of event-driven objects without encouraging you to accidentally form circular references. AnyEvent is a more minimalist API, and I like minimalism, but everything is anonymous subs and it's a minefield of circular reference opportunities.

      I would assume you are using some sort of event library?

      They should be, but they're not. It looks like they're using Net::WebSocket::Server, which implements its own select-based event loop.

Re: Perl alarm not going off when local websocket connection is active
by NERDVANA (Priest) on Apr 30, 2025 at 20:31 UTC
    I guess I should also post the link where I explored half a dozen ways to implement websockets and gave a presentation on it. If your codebase isn't too large yet, you might consider refactoring it with a different library. My favorite (after trying them all for my presentation) turned out to be Mojo