3

The context: a web application written in asp.net MVC + NHibernate. This is a card game where players play at the same time so their actions might modify the same field of an entity at the same time (they all do x = x + 1 on a field). Seems pretty classical but I don't know how to handle it.

Needless to say I can't present a popup to the user saying "The entity has been modified by another player. Merge or cancel ?". When you think that this is related to an action to a card, I can't interfere like this. My application has to internally handle this scenario. Since the field is in an entity class and each session has it own instance of the entity, I can't simply take a CLR lock. Does it mean I should use pessimistic concurrency so that each web request acting on this entity is queued until a player finished his action? In practical terms in means that each PlayCard request should use a lock?

Please, don't send me to NH doc about concurrency or alike. I'm after the technique that should be used in this case, not how to implement it in NH.

Thanks

Nicolas Cadilhac
  • 4,680
  • 5
  • 41
  • 62
  • Sounds like you want a system where multiple users all have the ability to modify an entity property (ON THE SERVER), without any concurrency issues - i.e. no locks, yet all updates succeed?? This of course is not possible - there are only 2 possibilities - either you lock and permit only 1 update to take place at a time, or you allow all updates to succeed, but accept that the last update will overwrite any earlier ones. – BonyT Jun 08 '11 at 14:00
  • My gut feeling is that I have to lock, not only the update, but the "select + update" sequence. Otherwise my x = x + 1 example won't work. I can't accept concurrent updates since with an initial x = 1, x will be 2 instead of 3 after all players act. So my question is more a "am I right saying that, for let's say the PlayCard action, all the web request should be session locked"? – Nicolas Cadilhac Jun 08 '11 at 14:04
  • I wouldn't use the db here. I'd have the clients ajax call the server - which will either allow the update or block it depending on which gets in there first - compare original value against current value. Server side, I'd hold the entity in a singleton for speed of response. – BonyT Jun 08 '11 at 14:18
  • Thx but not possible. x+1 is just an example and I have a lot of logic that can't be put client-side, only server-side. – Nicolas Cadilhac Jun 08 '11 at 14:23
  • who said client-side? I just said use ajax for speed - logic is still server side. – BonyT Jun 08 '11 at 14:26
  • Sorry, in this case, this is quite what I'm doing already since I send service calls to an asp.net mvc server from a silverlight app. – Nicolas Cadilhac Jun 08 '11 at 14:58

2 Answers2

1

You can try to apply optimistic locking in this way:

DB entity will have a column tracking entity version (nhibernate.info link).

If you get "stale version" exception while saving entity ( = modified by another user) - reload the entity and try again. Then send the updated value to the client.

As I understand your back-end receives request from the client, then opens session, does some changes and updates entities closing session. In this case no thread will hold one entity in memory for too long and optimistic locking conflicts shouldn't happen too often.

This way you can avoid having many locked threads waiting for operation to complete.

On the other hand, if you expect retries to happen too often you can try SELECT FOR UPDATE locking when loading your entity (using LockMode.Upgrade in NH Get method). Although I found the thread that discourages me from using this with SQL Server: SO link.

In general the solution depends on the logic of the game and whether you can resolve concurrency conflicts in your code without showing messages to users. I'd also made UI updating itself with the latest data often enough to avoid players acting on obsolete game situation and then be surprised with the outcome.

Community
  • 1
  • 1
AlexD
  • 5,011
  • 2
  • 23
  • 34
  • Thx. However this is difficult in the context of my mvc app where the session is automatically created at the start of the request and closed at the far end. Getting a stale entity means the session is useless and so I would have to manually reopen a session which breaks the unit of work I have in place. But anyway I understand your logic. – Nicolas Cadilhac Jun 08 '11 at 15:00
  • Hm, would it be possible for you to wrap this change in a smaller transaction and try to commit it immediately? The retry if it fails. You can keep the session open in the meantime. Not sure how important unit of work is in your case. – AlexD Jun 08 '11 at 15:19
  • Does it mean entity would have to be reattached to the sub-session? No problems with the many children the entity has? – Nicolas Cadilhac Jun 08 '11 at 17:32
1

It may make sense depending on your business logic to try second level caching. This may be a good depending on the length of the game and how it is played. Since the second level cache exists on the session factory level, the session factory will have to be managed according to the life time of the game. An Nh session can be created per request, but being spawned by a session factory configured for second level cache means data of interest is cached across all sessions. The advantage of using second level cache is that you can configure this on a class by class basis - caching only the entities your require. It also provides a variety of concurrency strategies depending on the cache provider. Even though this may shift the concurrency issue from the DB level to the NH session, this may give you a better option for dealing with your situation. There are gotchas to using this but it's suitability all depends on your business logic.

stantona
  • 3,260
  • 2
  • 24
  • 28
  • Interesting. I know nothing about the 2nd level cache to start. Are there interesting articles on the subject? – Nicolas Cadilhac Jun 08 '11 at 17:31
  • Here is a good article: http://nhibernate.hibernatingrhinos.com/28/first-and-second-level-caching-in-nhibernate And here is the section in the NH documentation: http://nhforge.org/doc/nh/en/index.html#performance-cache – stantona Jun 08 '11 at 17:36