5

I have read that cloned TClientDataSets are thread safe if the clones are read only (no posting of records or reloading of data)

Delphi - Is TClientDataset Thread Safe?

But I'm concerned about the CloneCursor method itself; the method ends by calling the source DataSet's SetNotifyCallback method, which passes a callback method to it's IDSCursor if FNotifyCallback is False:

procedure TCustomClientDataSet.SetNotifyCallback;
begin
  if not FNotifyCallback then
  begin
    Check(FDSCursor.SetNotifyCallBack(IntPtr(Self), @TCustomClientDataSet.NotifyCallback));
    FNotifyCallback := True;
  end;
end; 

In the unlikely event of two DataSets, A & B, in separate threads cloning to DataSet C at almost the same time (DataSet C's FNotifyCallback False), with A fractionally ahead of B. B starts executing C's SetNotifyCallBack after A has checked FNotifyCallback but before A has set FNotifyCallBack to True in the method shown above.

In this scenario, DataSet C's FDSCursor SetNotifyCallback method is being called almost simultaneously by two different threads; a method that is writing a reference to a variable inside IDSCursor (I assume; no luck finding source code). Admittedly both calls are asking the same reference to be stored, but as the title asks, is CloneCursor thread-safe?

Please accept my thanks in advance.

Community
  • 1
  • 1
CAnder
  • 127
  • 8
  • That's a good question, if only you would change your final paragraph to just one single question "Is `CloneCursor` really thread-safe or not?" – Jerry Dodge Aug 28 '15 at 00:04
  • 4
    If you are trying to clone the cursor from a single dataset in 2 threads then you have 2 threads accessing the same dataset which is not thread safe as a general rule. The same would apply to cloning the cursor from multiple datasets into a single one. In both instances you are not dealing with cloned TClientDataset's but sharing a single TClientDataset between two threads. Generally with this sort of thing you want to clone the item and then pass it to the thread. – Graymatter Aug 28 '15 at 00:12
  • @Jerry: That's asked very clearly in the question title, and the final paragraph seems pretty clear as well. – Ken White Aug 28 '15 at 02:36
  • @Jerry: On reflection the closing questions are unnecessary, and it would have been better left 'is CloneCursor thread-safe?'. I'll edit. – CAnder Aug 28 '15 at 05:29

2 Answers2

0

The concern presented in the question surrounded the albeit unlikely event of two threads, A and B, cloning to C at the same time, and these representing the first calls to C's CloneCursor method since C was opened (FNotifyCallback False). This leads to the possibility of C's IDSCursor SetNotifyCallback being called by two separate threads at the same time.

Based on Graymatter's comments, one solution is to ensure that the first call to C's CloneCursor happens within C's thread. Once this is done then FNotifyCallback is True, and remains so for as long as C remains open.

With FNotifyCallback True, the code for C's SetNotifyCallback resolves to the following instructions (on my PC):

006418CB  cmp byte ptr [ebx+$00000290],$00
006418D2  jnz $006418fa
{code to call IDSCursor SetNotifycallback}
006418FA 5B               pop ebx
006418FB C3               ret 

With FNotifyCallback True, SetNotifyCallback reduces to comparing the contents of FNotifyCallback with the value zero, before jumping to the pop and ret instructions.

At the risk of being beaten up, I believe once FNotifyCallback is True, then subsequent calls to SetNotifyCallback are thread safe.

While the body of the question focused on SetNotifyCallback, the question specifically addressed CloneCursor. The only other area of concern (that I can see) surrounds the assignment of the cloning DataSet's FDSBase to the source (C in this discussion) FDSBase. This will increment the source FDSBase interface's refcount.

On my PC this resolves to a single instruction to increment a memory location:

inc dword ptr [eax+$04] 

Which I'm assuming is atomic. Plus I cannot believe that making the incrementing and decrementing of an interface's refcount thread safe, was not one of the first things considered when interfaces were first proposed.

In summary, I believe CloneCursor to be thread safe once FNotifyCallback is True.

CAnder
  • 127
  • 8
  • That may be the case right now. I haven't looked into your answer in enough detail. The problem is that you are taking a chance going forward. Unless specifically stated that an object is thread safe, you will have to check each release of Delphi to ensure that it's still thread safe. The only way around this is to not share objects between threads. – Graymatter Aug 30 '15 at 16:57
0

Fairly old question, but I've got no good reply anywhere and had to dig a bit myself. So posting here for others who may still work with such things.

CloneCursor is not thread safe, at least in Delphi 2007, on which I've recently tested this. If you call CloneCursor in more than one thread, where each thread has its own cloned dataset, all of them cloning from the same source dataset, you will get AV or other errors. It is easy to reproduce this: just create two or more threads, which keep cloning the same dataset in a while loop, and you will get errors in a few seconds, at most. But you can just protect the call to CloneCursor with a critical section, and can work with your clone outside the critical section.

Now, the other way around that you suggested, cloning different sources to the same cloned dataset in two or more threads, doesn't make sense to me, and I would not even bother testing to see if that would raise errors or not, since such usage seems wrong by design already. If you need such usage, I would recommend going straight to CriticalSection.