Rollback in seat booking - grails

I am working on a seat booking module where the user checks whether the seats are available in exam and if yes he can proceed to payment page an then enter card details and pay. Now what I want is that when the payment page is opened seats available value should be reduced by 1 for some time(till session expires or user navigates to some other page).
If I do it using database transaction for the create action(MVC) it won't be possible as when the controller's action(In grails) will be triggered and database changes would be saved without any possibility of rollback.
Is it feasible to acquire seat lock using db or should I use some cron job to check it. Please suggest.

I'm assuming that an exam should never be over-booked. In other words, that exactness is more important than concurrency. In such a case, pessimistic locking strategy is the best bet. Note: by default, Grails uses optimistic locking.
You can use database pessimistic locking to assist with allocating seats, but on it's own, database locking is ill-suited for business transaction locking. As you implied, the lock is released when the transaction commits. So what you can do is implement your business transaction locking on top of database pessimistic locking.
Allocating/locking seats
I recommend using a service to allocate/deallocate seats so that all of the locking code is done in one place.
import groovy.time.TimeDuration
class ExamService {
TimeDuration allocationExpiration = new TimeDuration(0, 30, 0, 0) // 30 minutes
SeatsAllocation allocateSeats(long examId, long userId, int numberOfSeats) {
def exam = Exam.lock(examId)
if(exam.seatsAvailable >= numberOfSeats) {
exam.seatsAvailable = exam.seatsAvailable - numberOfSeats
exam.save()
return new SeatsAllocation(
exam: exam,
user: User.get(userId),
numberOfSeats: numberOfSeats,
purchased: false,
expiration: new Date() + allocationExpiration).save()
} else {
return null
}
}
}
In this example, allocateSeats() is called when the user decides to purchase a certain number of seats, but has not actually made the purchase yet. In this stage of the transaction, a SeatsAllocation represents a user's intent to purchase. The allocation has an expiration date which must be checked at the exact moment the purchase is made. If the allocation has expired, the purchase should be disallowed. The purchasing code is not shown, but basically make sure to set SeatsAllocation.purchased to true.
Deallocating/unlocking seats
This is a good place for the cron/Quartz job you mentioned. The job would retrieve the expired SeatsAllocations, make the seats available, and finally delete the allocations. In the following example, I have the code as part of the service, which the Quartz job can call.
import groovy.time.TimeDuration
class ExamService {
...
def deallocateExpiredSeats() {
SeatsAllocation.where {
purchased == false
expiration < new Date()
}.list([lock: true])
.each {
it.exam.seatsAvailable = it.exam.seatsAvailable + it.numberOfSeats
it.exam.save()
it.delete()
}
}
}
Note that using a scheduled job presents the problem of a delay between the SeatsAllocations expiring and those seats being made available. If the job runs too frequently it will conflict with the pessimistic locking used in allocateSeats(), reducing concurrency even further. On the other hand, if the job runs too infrequently, seats which technically are available cannot be allocated to potential customers because they'll be held hostage by the SeatsAllocations. You can choose the frequency based on the app's load.

Related

anylogic agent communication and message sending

In my model, I have some agents;
"Demand" agent,
"EnergyProducer1" agent
"EnergyProducer2" agent.
When my hourly energy demands are created in the Main agent with a function, the priority for satisfying this demand is belongs to "EnergyProducer1" agent. In this agent, I have a function that calculate energy production based on some situtations. The some part of the inside of this function is following;
**" if (statechartA.isStateActive(Operating.busy)) && ( main.heatLoadDemandPerHour >= heatPowerNominal) {
producedHeatPower = heatPowerNominal;
naturalGasConsumptionA = naturalGasConsumptionNominal;
send("boilerWorking",boiler);
} else ..... "**
Here my question is related to 4th line of the code. If my agent1 fails to satisfy the hourly demand, I have to say agent2 that " to satisfy rest of demand". If I send this message to agent2, its statechart will be active and the function of agent2 will be working. My question is that this all situations will be realized at the same hour ??? İf it is not, is accessing variables and parameters of other agent2 more appropiaote way???
I hope I could explain my problem.
thanks for your help in advance...
**Edited question...
As a general comment on your question, within AnyLogic environment sending messages is alway preferable to directly accessing variable and parameters of another agent.
Specifically in the example presented the send() function will schedule message delivery the next instance after the completion of the current function.
Update: A message in AnyLogic can be any Java class. Sending strings such as "boilerWorking" used in the example is good for general control, however if more information needs to be shared (such as a double value) then it is good practice to create a new Java class (let's call is ModelMessage and follow these instructions) with at least two properties msgStr and msgVal. With this new class sending a message changes from this:
...
send("boilerWorking", boiler);
...
to this:
...
send(new ModelMessage("boilerWorking",42.0), boiler);
...
and firing transitions in the statechart has to be changed to use if expression is true with expression being msg.msgString == "boilerWorking".
More information about Agent communication is available here.

Are stored procedures in Cosmos DB automatically retried on conflict?

Stored procedures in Cosmos DB are transactional and run under isolation snapshop with optimistic concurrency control. That means that write conflicts can occur, but they are detected so the transaction is rolled back.
If such a conflict occurs, does Cosmos DB automatically retry the stored procedure, or does the client receive an exception (maybe a HTTP 412 precondition failure?) and need to implement the retry logic itself?
I tried running 100 instances of a stored procedures in parallel that would produce a write conflict by reading the a document (without setting _etag), waiting for a while and then incrementing an integer property within that document (again without setting _etag).
In all trials so far, no errors occurred, and the result was as if the 100 runs were run sequentially. So the preliminary answer is: yes, Cosmos DB automatically retries running an SP on write conflicts (or perhaps enforces transactional isolation by some other means like locking), so clients hopefully don't need to worry about aborted SPs due to conflicts.
It would be great to hear from a Cosmos DB engineer how this is achieved: retry, locking or something different?
You're correct in that this isn't properly documented anywhere. Here's how OCC check can be done in a stored procedure:
function storedProcedureWithEtag(newItem)
{
var context = getContext();
var collection = context.getCollection();
var response = context.getResponse();
if (!newItem) {
throw 'Missing item';
}
// update the item to set changed time
newItem.ChangedTime = (new Date()).toISOString();
var etagForOcc = newItem._etag;
var upsertAccecpted = collection.upsertDocument(
collection.getSelfLink(),
newItem,
{ etag: etagForOcc }, // <-- Pass in the etag
function (err2, feed2, options2) {
if (err2) throw err2;
response.setBody(newItem);
}
);
if (!upsertAccecpted) {
throw "Unable to upsert item. Id: " + newItem.id;
}
}
Credit: https://peter.intheazuresky.com/2016/12/22/documentdb-optimistic-concurrency-in-a-stored-procedure/
SDK does not retry on a 412, 412 failures are related to Optimistic Concurrency and in those cases, you are controlling the ETag that you are passing. It is expected that the user handles the 412 by reading the newest version of the document, obtains the newer ETag, and retries the operation with the updated value.
Example for V3 SDK
Example for V2 SDK

Does beginning a transaction with a serializable isolation level block immediately?

Should any reads (SELECT) made to Table1 by another thread, after the transaction is started but before the UPDATE is executed, be blocked, or does the UPDATE need to first begin execution?
var _con = new SqliteConnection( "Data Source=" + FileName );
_con.Open();
SqliteCommand _cmd = _con.CreateCommand();
_cmd.CommandType = CommandType.Text;
_con.BeginTransaction(IsolationLevel.Serializable);
// UPDATE
_cmd.CommandText = "UPDATE Table1 SET field1 = 'a' WHERE Id = 1";
_cmd.ExecuteReader();
_cmd.Transaction.Commit();
In SQLite, all transactions (explicit and automatics ones) are serializable.
By default, SQLite's transactions are DEFERRED, which means that read/write locks are taken only when the database file actually needs to be read/written (and writes usually happen only when the caches is flushed when the transaction commits).
In journal rollback mode, readers and a writer block each other; in WAL mode, readers and a writer can access the DB concurrently, but there can still be only one writer.
However, Xamarin does not use the defaults; BeginTransaction() immediately starts a transaction in EXCLUSIVE mode, which means that it conflicts with all other concurrent transactions. (WAL mode makes no difference.)
Apparently, you are not supposed to use any concurrency in Xamarin.

Zendesk - CreateUser JobStatus Results is Null

So I am currently working with the ZenDesk API. I am creating many users using the batch CreateUser method that takes an array of up to 100 user objects. Now, for some reason, some users fail to generate. Therefore, I wanted to obtain the result of the JobStatus after creating the users so that I can identify the problem easily. The issue is that the result variable is null after performing the CreateUsers() method.
Some sample code:
public static void createEndUsers(Zendesk zd){
for(Organization org : zd.getOrganizations()){
List<User> usersList = getUsersPerOrg(org, 0)
JobStatus js = zd.createUsers(usersList);
List<T> resultElements = js.getResults();
}
}
Why is getResults() returning null in this instance? Is it because the operation has not yet been performed when it reaches that part of the code? How can I make sure that I "wait" until the result is ready before I try to access it?
If you have a look at the posssible values from org.zendesk.client.v2.model.JobStatus.JobStatusEnum you will notice that the job may be queued for execution or it could still be running at the time that the job status was returned by the operation org.zendesk.client.v2.Zendesk#createUsers(org.zendesk.client.v2.model.User...).
As can be seen from the Zendesk Documentation for createUsers Operation
This endpoint returns a job_status JSON object and queues a background job to do the work. Use the Show Job Status endpoint to check for the job's completion.
only when the job is completed, there will be a corresponding result delivered for the operation.
A possible solution in your case would be to check (possibly in a separate thread) every 500ms whether the job status is not queued or not running (otherwise said whether it completed) and if it successfully completed to retrieve the results.

Why doesn't my GORM object save to the database?

Consider the following code:
if (!serpKeyword) {
serpKeyword = new SerpKeyword(
keyword: searchKeyword,
geoKeyword: geoKeyword,
concatenation: concatenation,
locale: locale
)
serpKeyword.save(flush: true, failOnError: true)
}
serpService.submitKeyword(serpKeyword, false)
Here's the submitKeyword method:
#Transactional(propagation = Propagation.REQUIRES_NEW)
boolean submitKeyword(keywordToSubmit, boolean reset) {
def keyword = SerpKeyword.get(keywordToSubmit.id)
No error is raised when I call serpKeyword.save, but when I get into the submitKeyword method, SerpKeyword.get(keywordToSubmit.id) returns null. What could be preventing this from saving?
Edit
Changing REQUIRES_NEW to REQUIRED seems to do the trick. Here's what I think is happening.
The code that calls serpService.submitKeyword is located within a service method. From what I understand, service method's have a default propagation strategy of REQUIRED. Since all this database writes are happening within the context of a transaction, the writes are queued up in the database, but not actually executed against the database until the transaction is completed, according to the docs:
Note that flushing is not the same as committing a transaction. If
your actions are performed in the context of a transaction, flushing
will execute SQL updates but the database will save the changes in its
transaction queue and only finalize the updates when the transaction
commits.
We call serpService.submitKeyword before our transaction is actually finished. That method starts a completely new transaction where our serpKeyword is not available. Changing it to REQUIRED works because we are now operating within the context of our parent transaction.
I think some part of the stack is being lazy. I've ran into this behavior with Hibernate, if that's what you're using. I'm not sure it's an approved maneuver, but you could clear the session before calling submitKeyword like so:
long keywordId = serpKeyword.id
SerpKeyword.withSession{it.clear()}
serpService.submitKeyword(keywordId, false)
And then change the method to:
#Transactional(propagation = Propagation.REQUIRES_NEW)
boolean submitKeyword(keywordId, boolean reset) {
def keyword = SerpKeyword.get(keywordId)
Then I bet the .get() will work. If you have other objects in the session you need. You will need to lift those out by storing their id's and .get()ing them as well.

Resources