Is there a way to have the server process API requests one at a time?


#1

As an example, lets say I want to clone a type instance. Each clone has a name following the scheme originalName-copyX where x is the number of copies. We do not want two clones with the same name, so we should only do one cloning at a time.

Is there a way to specify that behavior for a server side method?


#2

I think you have to implement the logic yourself with a combination of copyObj and fetch, e.g.

cloneObj: function(this: !mixing Persistable, type: !Type): !Obj

which can be implemented as

function cloneObj(type: !Type) {
  var cloneInstance = this.copyObj(type);
  var prefix = cloneInstance.name + '-copy';
  var previousClone = type.fetch({
    filter: 'startsWith(name, "'+prefix+'")',
    order: 'descending(name)',
    include: 'name',
    limit: 1
  }).at('objs[0]');
  if(_.isUndefined(previousClone)) {
    cloneInstance.name = prefix + '1';
  }else {
    cloneInstance.name = prefix + parseInt(previousClone.replace(prefix, ''));
  }
}

Hope that would help.


#3

This is a good way to make the clone, but I believe there is a race condition here. If two users on different machines on the same tag clone the same instance at the same time, they would have equal previousClone values, and therefore both generate a clone with the same name. Is there a way to have the server throttle these requests so that only one gets processed at a time?


#4

I’m not sure we have mechanisms for synchronization, but to be sure that none else could create another clone with same id use unique in the db annotation like:

@db(unique=["name"])
entity type TargetType {
  . . .
}

This will guarantee unicity for name and as result the second user attempting to create a clone with same clone name will fail.


#5

@Alexander Bauer you could acquire a DbLock with a specific key and pass the lambda function that you need to synchronize. Its pretty straight forward, you can read examples / documentation from lock.c3doc. It also has examples of implementing your own custom locking logic (not using existing db lock) and examples of how to use the DbLock / steps to debug / log statements to search for /etc.

e.g.

DbLock.key(CronQueue.TYPE_NAME)
      .multiTenant()
      .doNotThrowOnFailure()
      .timeout(5 * 60 * 1000) // 5 minutes
      .synchronize((key) -> {
         // CRITICAL SECTION - any custom code that needs to be protected    
         CronQueue.invalidateCron();
         return null;
      });

but in principal, i agree with david, each unit of work should be idempotent, that is the only way you can scale. If you want I can go over the use case with you to see if you can get around without acquiring db lock