String timerKey = getTimerKey(timerID)
log.trace("Register handler for gameTimer=${timerKey}")
vertx.registerHandler(timerKey, { m ->
log.trace("Canceling timer")
vertx.cancelTimer(timerID)
pokerGame.getTimersSetForGame().remove(gameTimerKey)
vertx.unregisterHandler(timerKey, { unregisterDone ->
log.trace("Unregister handler from cancel for gameTimer=${timerKey}")
})
})
--
You received this message because you are subscribed to the Google Groups "vert.x" group.
To unsubscribe from this group and stop receiving emails from it, send an email to vertx+un...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
void unregisterHandler(String address, Closure handler) {
eventBus.unregisterHandler(address, handler)
}
void unregisterHandler(String address, Closure handler, Closure resultHandler) {
eventBus.unregisterHandler(address, handler, resultHandler)
}
public void registerForEvents(String name, GameEvent event, Closure handler) {
log.trace("'${name}' registering for events of type '${event}' on address '${event.fqen}'")
eventBus.registerHandler(event.fqen, handler)
}
void registerHandler(String address, Closure handler, Closure resultHandler) {
eventBus.registerHandler(address, handler, resultHandler)
}
void registerHandler(String address, Closure handler) {
eventBus.registerHandler(address, handler)
}
But looking at it I think I need to call the one that takes two handlers. Although what is the first handler do?
I want the resultHandler as the closure.
Mark
On Friday, May 1, 2015 at 2:03:10 PM UTC-7, bytor99999 wrote:Sorry we have a Wrapper class.
In the wrapper class we have...
void unregisterHandler(String address, Closure handler) { eventBus.unregisterHandler(address, handler) } void unregisterHandler(String address, Closure handler, Closure resultHandler) { eventBus.unregisterHandler(address, handler, resultHandler) } public void registerForEvents(String name, GameEvent event, Closure handler) { log.trace("'${name}' registering for events of type '${event}' on address '${event.fqen}'") eventBus.registerHandler(event.fqen, handler) } void registerHandler(String address, Closure handler, Closure resultHandler) { eventBus.registerHandler(address, handler, resultHandler) } void registerHandler(String address, Closure handler) { eventBus.registerHandler(address, handler) }
private String startTimerInternalGame(PokerGame pokerGame, String gameTimerKey, Long timeout, String method, Map<String, Object> args) {
String tableID = pokerGame.getTableID()
Long timerID = vertxWrapper.setTimer(timeout, { Long timerID ->
final String timerKey = getTimerKey(timerID)
try {
gameStateManager.doGameMutationWithLock(new BasicGameStateMutation(tableID, method, args))
pokerGame.getTimersSetForGame().remove(gameTimerKey)
log.trace("Trying to unregister handler for gameTimer=$timerKey")
vertxWrapper.unregisterHandler(timerKey, {nothing ->}, { unregisterDone ->
log.trace("Unregister handler after firing for gameTimer=$timerKey")
})
pokerGame.firedTimers.put(gameTimerKey, timerKey)
} catch (Throwable e) {
log.error("Timer mutation failed for gameTimer=$timerKey", e)
}
})
String timerKey = getTimerKey(timerID)
log.trace("Register handler for gameTimer=${timerKey}")
vertxWrapper.registerHandler(timerKey, { m ->
log.trace("Canceling timer")
vertxWrapper.cancelTimer(timerID)
pokerGame.getTimersSetForGame().remove(gameTimerKey)
vertxWrapper.unregisterHandler(timerKey, {nothing ->}, { unregisterDone ->
log.trace("Unregister handler from cancel for gameTimer=${timerKey}")
})
})
return timerKey
}
So why the first? Isn't the Handler going to be unregistered via the Address in the first parameter?
Anyway, I don't think my handlers are being unregistered. Let me post the whole method and maybe you can see what I am missing. One note, is my re-write simplification of code so far, I haven't yet seen a missed timer fire.
Thanks
private String startTimerInternalGame(PokerGame pokerGame, String gameTimerKey, Long timeout, String method, Map<String, Object> args) { String tableID = pokerGame.getTableID() Long timerID = vertxWrapper.setTimer(timeout, { Long timerID -> final String timerKey = getTimerKey(timerID) try {
gameStateManager.doGameMutationWithLock(new BasicGameStateMutation(tableID, method, args))
pokerGame.getTimersSetForGame().remove(gameTimerKey) log.trace("Trying to unregister handler for gameTimer=$timerKey") vertxWrapper.unregisterHandler(timerKey, {nothing ->},
{ unregisterDone -> log.trace("Unregister handler after firing for gameTimer=$timerKey") }) pokerGame.firedTimers.put(gameTimerKey, timerKey) } catch (Throwable e) { log.error("Timer mutation failed for gameTimer=$timerKey", e) } }) String timerKey = getTimerKey(timerID) log.trace("Register handler for gameTimer=${timerKey}") vertxWrapper.registerHandler(timerKey, { m -> log.trace("Canceling timer") vertxWrapper.cancelTimer(timerID) pokerGame.getTimersSetForGame().remove(gameTimerKey) vertxWrapper.unregisterHandler(timerKey, {nothing ->}, { unregisterDone -> log.trace("Unregister handler from cancel for gameTimer=${timerKey}") }) }) return timerKey }
Mark
In Vert.x 2.x you need both the address AND the handler in order to unregister it - this is because an address can have many handlers registered with it, so just supplying the address is not enough information.
http://vertx.io/core_manual_groovy.html#registering-and-unregistering-handlers
So when you unregister you must use the same address and the *exact same handler* to unregister it as you used to register it.
What is the following method? The name worries me a bit. How long does it take to execute? Is it blocking? withLock implies there is some kind of global lock - will this scale. The fact that this is in a timer handler and you say you have a million or more timers worries me...
gameStateManager.doGameMutationWithLock(new BasicGameStateMutation(tableID, method, args))
boolean gotLock = false;
try {
if (HazelcastUtil.getLocksMap().tryLock(gameID, LOCK_TIMEOUT, TimeUnit.SECONDS)) {
gotLock = true;
PokerGame game = getGame(gameID);
gameStateMutation.game = game;
gameStateMutation.mutate();
List<Message> messages = game.getMessages();
game.clearMessages();
saveGame(game);
vertxWrapper.dispatchAll(messages)
return messages;
} else {
throw new RuntimeException("Could not get lock for game " + gameID + " in " + LOCK_TIMEOUT + " seconds");
}
} catch (Exception e) {
log.error("Game state mutation error: ", e)
throw new RuntimeException("Game state mutation failed gameID=$gameID", e);
} finally {
if (gotLock) {
HazelcastUtil.getLocksMap().forceUnlock(gameID);
HazelcastUtil.getLocksMap().remove(gameID);
/*Object[] args = [swatch.getTime(), gameStateMutation.gameID, method];
if (swatch.getTime() > 1000) {
log.warn("Took {}ms to finish game state mutation for table {} and method {}", args);
}*/
}
}
In Vert.x 2.x you need both the address AND the handler in order to unregister it - this is because an address can have many handlers registered with it, so just supplying the address is not enough information.
http://vertx.io/core_manual_groovy.html#registering-and-unregistering-handlers
So when you unregister you must use the same address and the *exact same handler* to unregister it as you used to register it.
I was afraid of that because I don't have the handler defined till after the timer has been set because the address of the handler has to include the timerID which isn't set till the timer is started. And when the timer fires I need unregister the hander inside the timer firing handler, which will have the timerID but hasn't registered the handler yet for that address. Cart before the horse.
What is the following method? The name worries me a bit. How long does it take to execute? Is it blocking? withLock implies there is some kind of global lock - will this scale. The fact that this is in a timer handler and you say you have a million or more timers worries me...
gameStateManager.doGameMutationWithLock(new BasicGameStateMutation(tableID, method, args))
This is the code to actually do something in the game. It is a State Machine, and basically just calling a method on the PokerTable class to update its state. The code that is running inside the lock might take 1 ms at most.
We also have many Threads in the thread pool. We have had at least 4000 bots playing on tables for a few hours with this code (The state machine and Locks wasn't my idea, I have to add)
But whether it is locking there or in a database, it is still needed to have that particular game/table locked till the mutation is done. Here is the locking code, so you can see what we do.
boolean gotLock = false; try { if (HazelcastUtil.getLocksMap().tryLock(gameID, LOCK_TIMEOUT, TimeUnit.SECONDS)) { gotLock = true; PokerGame game = getGame(gameID); gameStateMutation.game = game; gameStateMutation.mutate(); List<Message> messages = game.getMessages(); game.clearMessages(); saveGame(game); vertxWrapper.dispatchAll(messages) return messages; } else { throw new RuntimeException("Could not get lock for game " + gameID +
" in "< /span> + LOCK_TIMEOUT + " seconds");
} } catch (Exception e) { log.error("Game state mutation error: ", e) throw new RuntimeException("Game state mutation failed gameID=$gameID", e); } finally { if (gotLock) { HazelcastUtil.getLocksMap().forceUnlock(gameID); HazelcastUtil.getLocksMap().remove(gameID); /*Object[] args = [swatch.getTime(), gameStateMutation.gameID, method]; if (swatch.getTime() > 1000) { log.warn("Took {}ms to finish game state mutation for table {} and method {}", args); }*/ } }
Thanks
Mark
On 02/05/15 17:13, bytor99999 wrote:
In Vert.x 2.x you need both the address AND the handler in order to unregister it - this is because an address can have many handlers registered with it, so just supplying the address is not enough information.
http://vertx.io/core_manual_groovy.html#registering-and-unregistering-handlers
So when you unregister you must use the same address and the *exact same handler* to unregister it as you used to register it.
I was afraid of that because I don't have the handler defined till after the timer has been set because the address of the handler has to include the timerID which isn't set till the timer is started. And when the timer fires I need unregister the hander inside the timer firing handler, which will have the timerID but hasn't registered the handler yet for that address. Cart before the horse.
This should be easily fixable by moving factoring stuff out into different methods.
What is the following method? The name worries me a bit. How long does it take to execute? Is it blocking? withLock implies there is some kind of global lock - will this scale. The fact that this is in a timer handler and you say you have a million or more timers worries me...
gameStateManager.doGameMutationWithLock(new BasicGameStateMutation(tableID, method, args))
This is the code to actually do something in the game. It is a State Machine, and basically just calling a method on the PokerTable class to update its state. The code that is running inside the lock might take 1 ms at most.
I'd doubt that. Looks like you're doing a Hazelcast tryLock operation, which is likely to require a network round trip.
Do you have one state machine per game?
Also.. how does
PokerGame game = getGame(gameID);
Actually get the game?
I presume this can run on any node of the cluster, or why else would you use a Hazelcast lock, so presumably the Game state might be on another node too? So maybe another network round trip to get that...
I'd suggest that this architectural approach is quite un-Vert.x-y and will scale poorly. A more Vert.x-y approach would be to model each game as a verticle instance and then you can interact with that game (e.g. mutate its state) by sending it messages. You wouldn't need any locking as verticles are inherently single threaded, and you wouldn't need worker verticles. You also wouldn't need to maintain game state somewhere and retrieve it, since the state would be in the verticle. This is more of an actor-like approach to the problem.
Just to do a little maths here - let's say the code in the timer, takes on average 5 ms to execute.
If you have 4 million timers in 10 minutes that means, in total it's going to take 20 million ms in total to execute, every 10 minutes.
20 million ms = 333 minutes! So you'll need to cram in 333 minutes of CPU processing
into 10 minutes of wall clock time - well that's clearly impossible on a single thread (without breaking the laws of physics) so you'll need at least 34 cores just to stay on top of it, and that's assuming all the work is perfectly parallelisable, which it's unlikely to be because you're using hazelcast to manage locks...
Even if processing does take 1ms as you say, you still have a big issue. It seems quite likely to me based on your description this could be the source of your timer issues. I strongly suspect they are backing up because you just can't process them in time thus appearing to get "lost".
Of course, I could be completely wrong as I haven't seen your actual system, but making a judgement based on what I know so far, I'd say it's a strong possibility.
We also have many Threads in the thread pool. We have had at least 4000 bots playing on tables for a few hours with this code (The state machine and Locks wasn't my idea, I have to add)
But whether it is locking there or in a database, it is still needed to have that particular game/table locked till the mutation is done. Here is the locking code, so you can see what we do.
boolean gotLock = false; try { if (HazelcastUtil.getLocksMap().tryLock(gameID, LOCK_TIMEOUT, TimeUnit.SECONDS)) { gotLock = true; PokerGame game = getGame(gameID); gameStateMutation.game = game; gameStateMutation.mutate(); List<Message> messages = game.getMessages(); game.clearMessages(); saveGame(game); vertxWrapper.dispatchAll(messages) return messages; } else {
throw new RuntimeException("Could not get lock for game " + gameID + " in "& lt; /span> + LOCK_TIMEOUT + " seconds");
} } catch (Exception e) { log.error("Game state mutation error: ", e) throw new RuntimeException("Game state mutation failed gameID=$gameID", e); } finally { if (gotLock) { HazelcastUtil.getLocksMap().forceUnlock(gameID); HazelcastUtil.getLocksMap().remove(gameID); /*Object[] args = [swatch.getTime(), gameStateMutation.gameID, method]; if (swatch.getTime() > 1000) { log.warn("Took {}ms to finish game state mutation for table {} and method {}", args); }*/ } }
Thanks
Mark
final Map<String, Closure> handlers = new HashMap<>()
And look up the Closure by the address, in which we don't use the timerID in the address, instead we use a UUID as the address.
Mark
If we put them all in a Map with a key. Because there is only one instance of SimpleGameTimerManagerImpl