Cluster opponent matching

54 views
Skip to first unread message

whizz...@gmail.com

unread,
Apr 6, 2014, 8:58:59 AM4/6/14
to action...@googlegroups.com
Hello heroes,
I've been building a game app with AH and so far I'm loving it - the whole structure of the framework is great and working with it is easy and productive. However, I've stumbled upon a problem - how to do opponent matching (or in general websocket connections pairing) while running AH in cluster mode. I want to use an existing AH functionality instead of writing my own. I consider the following options:
1) Put all the connections in a redis set and using a task to clear it every 5 minutes and match what I've taken from the set. However, I read that the time I set is when "a task is “allowed” to run, not when it will run", plus that the workers sleep between the runs and need some time to awake. So I'm afraid sometimes the matching task may take too long and the clients would have to wait.
2) Store the connections in the cache and on each new one check if there exists one so that the two can be paired. Here I'm not sure how the reads/updates to the cache can be made atomic, since the doc says "Keep in mind that many clients/servers can access a cached value simultaneously, so build your actions carefully not to have conflicting state."

I know this may not be exactly AH-related question but in case a good general solution for the opponent matching problem comes up here I (or we) can try to make it a plugin and allow for more-pleasant round-based game development with actionhero.

Evan Tahler

unread,
Apr 6, 2014, 4:54:31 PM4/6/14
to action...@googlegroups.com, whizz...@gmail.com
Actionhero's "rooms" exist across servers (as long as all servers share the same redis backend).  This means all connections (connected to any server in your cluster) can join/chat/get status, etc about a room.  This simplest way to test this out is to make 2 local actionhero projects, boot them on different ports, and use the sample chat page to see messages to clients in both servers.  That said, the array `api.connections.connections` on any given server will only reflect the connections locally connected to that specific server.

I think you have 2 options here.  If you want to use *only* actionhero semantics, you can have connections who need to be matched join a specific chat room (lobby).  Any client/server can get a list of connections in that room with roomStatus.  You can then match people and move them out of the "lobby" into their game.  

Alternatively, since you already noted, you can keep unmatched connections in a redis list and pop them out as you match them.  The rescue worker sleep timeout is configurable if 5s is too long for you app.  You are correct that using `api.cache` seems like the wrong data store here. 

whizz...@gmail.com

unread,
Apr 9, 2014, 4:31:49 PM4/9/14
to action...@googlegroups.com, whizz...@gmail.com
Thank you, I decided to go with the redis connection pool and wipe it periodically with a task. I have another related question, though. Is there a way to attach my own custom event on websocket connection/disconnection? I found the original on connection method in servers/websocket.js but I don't want to alter the internal files so that I can easily update AH and generally have a better structure?

Evan Tahler

unread,
Apr 9, 2014, 4:33:08 PM4/9/14
to action...@googlegroups.com, whizz...@gmail.com, whizz...@gmail.com
--
You received this message because you are subscribed to the Google Groups "actionHero.js" group.
To unsubscribe from this group and stop receiving emails from it, send an email to actionhero-j...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

whizz...@gmail.com

unread,
Apr 10, 2014, 5:03:14 PM4/10/14
to action...@googlegroups.com, whizz...@gmail.com
Thank you, that worked very well. Is there a similar way to alter the websocket connection object using AH middleware? I have code similar to this:

incoming: function (message, request, callback) {
// Let non-subscribe messages through
if (message.channel !== '/meta/subscribe')
return callback(message);

var token = message.ext && message.ext.token ? message.ext.token : '';
if (verifyToken(token)) {
message.error = "Authentication failed. Please authenticate again.";
} else {
// alter connection here by adding data to the message
message.user = 'test';
}

callback(message);
}
However, I haven't found a way to alter the message in a way that I later have the user object in the websocket connection:

var onWebsocketConnection = function (connection) {
console.log(connection.user); }
api.connections.createCallbacks.push(onWebsocketConnection);

whizz...@gmail.com

unread,
Apr 10, 2014, 5:19:58 PM4/10/14
to action...@googlegroups.com, whizz...@gmail.com
Also, given the authentication function above and the message.error being set, is there a way to not connect this user at all? (because now the user can't chat but they still show up in the onWebsocketConnection = function (connection) middleware.
I hope I'm not asking too easy questions but I'm still learning this great framework :)

Evan Tahler

unread,
Apr 12, 2014, 5:52:08 PM4/12/14
to action...@googlegroups.com, whizz...@gmail.com
I strongly suggest that you don't modify the faye middleware the `websocket` server relies on.  There's a lot going on in there (authentication, ID generation, etc) which the rest of actionhero relies on.  

Websocket authentication should revolve around either chat room authenticaion, or authentication against a pre-set value on the connection itself.  Create an `authenticate` action and within it, set `connection._original_connection.autneticated = true`.  You can use that both in later actions and as an authentication key to chat rooms. 

There is no way to prevent a user from connecting, but you can kick them out (send a disconnect event to the client).  This branch adds functionality to the server(s) so that when you call `connection.disconnect()` it will also "cut the wire" to the client as well.  Keep in mind the client can then just reconnect again, and now you are in DDOS/Rate Limiting territory.  

whizz...@gmail.com

unread,
Apr 14, 2014, 3:25:29 PM4/14/14
to action...@googlegroups.com, whizz...@gmail.com
Thanks for the response. I've been digging into the code and I think found a way to log the person in and modify the connection object without modifying AH - send the clients token as
setupChannel: '/client/websocket/_incoming/' + token
in actionheroClient's options. Then I'll authenticate the users and disconnect the ones that don't have a valid token in the middleware. My question is is this a proper way to do this (the tokens are supposed to be unique). Can this break the inner workings of AH?
Reply all
Reply to author
Forward
0 new messages