Hi, I have a few questions wrt multi-threading, on behalf of one of our users.
---
We are seeing some strange behaviour in Dataminer where attempting to access tables results in errors citing an inconsistent number of rows
2023/08/24 02:09:56.417|SLProtocol - 11336 - Resource Manager|14568|ARRAY::SetAt|ERR|-1|[Cell update] Table [JobPlanningFileStatusTable [11500]] [Column: JobPlanningFileStatusTableLastUpdateNM01 [11505]] Set on row 1511 [only 1510 row(s) available]
I suspect this could be because these tables are updated by multiple threads and we haven’t put any locks in to ensure they are threadsafe.
I have a couple of specific questions on how Dataminer handles concurrent threads:
- Scenario 1:
- A thread created explicitly in .NET updates Parameter1
- Action1 is triggered by the update to Parameter1 and adds a new row into Table1
- Action2 is triggered by Timer1 and deletes some rows from Table1
- Could Action1 and Action2 trigger in parallel (and so be updating Table1 at the same time)?
- Scenario 2:
- Element2 updates Parameter1 which is on Element1
- Action1 on Element1 is triggered by the update to Parameter1 and adds a row to Table1
- Action2 on Element1 is triggered by Timer1 and deletes some rows from Table1
- Could Action1 and Action2 trigger in parallel (and so update Table1 at the same time)?
---
Thanks!
Hi Ruben,
We always try to maintain the integrity of the data by using the necessary locking in our server code while trying to keep parallelization at a maximum. And thus, there may be some scenarios that may be too lenient.
I would expect SLProtocol to lock the entire table access when doing a set, preventing the second thread from making changes until the first thread is done. However, from your input, it seems like the lock is happening on column or row level for each individual change without locking the overall operation. Your cell update seems to have resolved the necessary row index without staying locked to perform the actual update.
Would have to check the documentation to see what the recommended approach is for QActions, but I would suggest adding locks in your code when writing to tables, or on a per-table basis if a higher throughput is needed.
Edit: I seem to have partially misunderstood your question, here's more information that should answer your questions:
Only one QAction can be executed at a time unless one of these options is used in the connector:
- https://docs.dataminer.services/develop/schemadoc/Protocol/Protocol.Threads.Thread.html
- options attribute - queued
Then, within the QAction it is also possible to launch additional threads, which can probably in turn, execute another QAction by setting a parameter.
I believe that your first scenario is using an additional thread within the QAction, so that is a potential clash. Your second scenario doesn't seem to fall into any of these cases, since the update to parameter 1 would be executed on the same protocol thread as the group/action of timer 1.
Hi David,
Yes, those will be sufficient. It’s mostly a matter of finding which blocks of code need to be locked without losing the advantage of having multiple threads by locking everything.
Do be mindful of correctly providing an object to lock on to the different QActions. It needs to be the same instance, or one QAction will not prevent another from executing the problematic code.
Thankyou Floris, that does answer my questions.
When you refer to adding necessary locking, is it sufficient to just use the standard locking features that come with .NET such as lock{}, Monitor and Semaphores?