Atomically setting NextValue counter?

Is there a way to atomically set a NextValue counter if the current value is lower than some specified value? This could also take the form of GetNextSequence(seqName, minValue). There doesn’t seem to be a way to do this atomically, so any attempt to do this within a BPM creates a race condition.

I guess you mean Automatically?

I think your solution is related to this post.

2 Likes

I mean atomically. I’d like to write something like this, but it contains a potential race condition:

var nv = new NextValue(Db);
var foo = nv.GetNextSequence("FOO");
if(foo < MIN)
{
    foo = MIN;
    NextValue.SetSequenceCurrentValue(Db, "FOO", foo);
}

I was hoping there was some undocumented method that would do this atomically, like

var nv = new NextValue(Db);
var foo = nv.GetNextSequence("FOO", MIN); // method doesn't exist

I think @josecgomez may have originally shared this below code:

Erp.Internal.Lib.CompanySequence.GetNextCompanySeq(CompanyID, SequenceName)

Erp.Internal.Lib.SetCurrentCompanySequence(CompanyID, SequenceName, currentValue)

2 Likes

Sorry, I don’t see how that addresses my question. I’m looking for a way to atomically set the current value if the current value is lower than some specified value. The goal is to avoid the need to take the system offline to bump a counter.

You don’t have to take anything offline - you can call either of those 2 methods at any time.

But you cannot call one and then conditionally call the other atomically. (Unless there’s something I don’t know about the framework, like all BPMs run in a single thread.)

Check out transactionscope.

https://blog.goyello.com/2016/11/30/let-net-framework-care-about-transactions-handling-for-you-by-implementing-ienlistmentnotification/

How about this?

var nv = new NextValue(Db);
int foo;
while ((foo = nv.GetNextSequence("FOO")) < MIN)
{
    Console.WriteLine(foo);
}

GetNextSequence() will get the next value and rev the counter so no one else can get the same value. I think that satisfies your atomic requirements.

As far as the comments regarding transactions, that’s good advice but does not apply in this case. The values are rev’d outside the transaction (otherwise it would create a lot of database contention).

3 Likes

That makes sense. I wonder how long it will take to increment a million times…

Doing just about anything a million times will be relatively slow, especially if it includes database access. If it’s a one time hit it’s likely acceptable. If your use case includes the sequence getting out of sync by a million regularly you will want to look at GetSequenceBlock("foo", 1000000) which can increment the sequence by an arbitrary block size. But then you’ll have to deal with potentially two threads incrementing by that much at once and then you could end up with a big gap in your sequence. You can experiment with doing a block size of half the gap minus 1 and then incrementing by one the rest of the way to avoid that. I’ll leave this as an exercise to the reader :wink:

My use case is that we have counters that we might want to bump up to the next million once a year or something like that. My original idea was to put the minimum values in the same BPM that uses the counter values, because that’s where I expect someone to go looking for them. But since bumping the counters will probably be a rare occurrence, it’s probably easier to put a comment in the BPM with the instruction to take Epicor offline and manually update the counter in Ice.SysSequence. Either that or count up to the new minimum and include a comment saying to expect a long delay.

Could you use two counters: your million counter and the detail counter and just concatenate the results? Bumping the million counter would be immediate.

1 Like