views:

1771

answers:

8

Hi

This is a bit of a long question, but here we go. There is a version of FormatDateTime that is said to be thread safe in that you use

GetLocaleFormatSettings(3081, FormatSettings);

to get a value and then you can use it like so;

FormatDateTime('yyyy', 0, FormatSettings);

Now imagine two timers, one using TTimer (interval say 1000ms) and then another timer created like so (10ms interval);

CreateTimerQueueTimer
(
  FQueueTimer, 
  0, 
  TimerCallback, 
  nil, 
  10, 
  10, 
  WT_EXECUTEINTIMERTHREAD
);

Now the narly bit, if in the call back and also the timer event you have the following code;

for i := 1 to 10000 do
begin
  FormatDateTime('yyyy', 0, FormatSettings);
end;

Note there is no assignment. This produces access violations almost immediatley, sometimes 20 minutes later, whatever, at random places. Now if you write that code in C++Builder it never crashes. The header conversion we are using is the JEDI JwaXXXX ones. Even if we put locks in the Delphi version around the code, it only delays the inevitable. We've looked at the original C header files and it all looks good, is there some different way that C++ uses the Delphi runtime? The thread safe version of FormatDatTime looks to be re-entrant. Any ideas or thoughts from anyone who may have seen this before.

UPDATE:

To narrow this down a bit, FormatSettings is passed in as a const, so does it matter if they use the same copy (as it turns out passing a local version within the function call yeilds the same problem)? Also the version of FormatDateTime that takes the FormatSettings doesn't call GetThreadLocale, because it already has the Locale information in the FormatSettings structure (I double checked by stepping through the code).

I made mention of no assignment to make it clear that no shared storage is being accessed, so no locking is required.

WT_EXECUTEINTIMERTHREAD is used to simplify the problem. I was under the impression you should only use it for very short tasks because it may mean it'll miss the next interval if it is running something long?

If you use a plain old TThread the problem doesn't occur. What I am getting at here I suppose is that using a TThread or a TTimer works but using a thread created outside the VCL doesn't, that's why I asked if there was a difference in the way C++ Builder uses the VCL/Delphi RTL.

As an aside this code as mentioned before also fails (but takes longer), after a while, CS := TCriticalSection.Create;

  CS.Acquire;
  for i := 1 to LoopCount do
  begin
    FormatDateTime('yyyy', 0, FormatSettings);
  end;
  CS.Release;

And now for the bit I really don't understand, I wrote this as suggested;

function ReturnAString: string;
begin
  Result := 'Test';
  UniqueString(Result);
end;

and then inside each type of timer the code is;

  for i := 1 to 10000 do
  begin
    ReturnAString;
  end;

This causes the same kinds of failiures, as I said before the fault is never in the same place inside the CPU window etc. Sometimes it's an access violation and sometimes it might be an invalid pointer operation. I am using Delphi 2009 btw.

UPDATE 2:

Roddy (below) points out the Ontimer event (and unfortunately also Winsock, i.e. TClientSocket) use the windows message pump (as an aside it would be nice to have some nice Winsock2 components using IOCP and Overlapping IO), hence the push to get away from it. However does anyone know how to see what sort of thread local storage is setup on the CreateQueueTimerQueue?

Thanks for taking the time to think and answer this problem.

+1  A: 

As DateTimeToString which FormatDateTime calls uses GetThreadLocale, you may wish to try having a local FormatSettings variable for each thread, maybe even setting up FormatSettings in a local variable before the loop.

It may also be the WT_EXECUTEINTIMERTHREAD parameter which causes this. Note that it states it should only be used for very short tasks.

If the problem persists the problem may actually be elsewhere, which was my first hunch when I saw this but I don't have enough information about what the code does to really determine that.

Details about where the access violation occurs may help.

PetriW
+1  A: 

Are you sure this actually has anything to do with FormatDateTime? You made a point of mentioning that there is no assignment statement there; is that an important aspect of your question? What happens if you call some other string-returning function instead? (Make sure it's not a constant string. Write your own function that calls UniqueString(Result) before returning.)

Is the FormatSettings variable thread-specific? That's the point of having the extra parameter for FormatDateTime, so each thread has its own private copy that is guaranteed not to be modified by any other thread while the function is active.

Is the timer queue important to this question? Or do you get the same results when you use a plain old TThread and run your loop in the Execute method?

You did warn that it was a long question, but it seems there are a few things you could do to make it smaller, to narrow down the scope of the issue.

Rob Kennedy
A: 

I wonder if the RTL/VCL calls you're making are expecting access to some thread-local storage (TLS) variables that aren't correctly set up whn you invoke your code via the timer queue?

This isn't the answer to your problem, but are you aware that TTimer OnTimer events just run as part of the normal message loop in the main VCL thread?

Roddy
+3  A: 

I am not sure if it is good form to post an "Answer" to my own question but it seemed logical, let me know if that is uncool.

I think I have found the problem, the thread local storage idea lead me to follow a bunch of leads and I found this magical line;

IsMultiThread := True;

From the help;

"IsMultiThread is set to true to indicate that the memory manager should support multiple threads. IsMultiThread is set to true by BeginThread and class factories."

This of course is not set by using a single Main VCL thread using a TTimer, However it is set for you when you use TThread. If I set it manually the problem goes away.

In C++Builder, I do not use a TThread but it appears by using the following code;

if (IsMultiThread) {
  ShowMessage("IsMultiThread is True!");
}

that is it set for you somewhere automatically.

I am really glad for peoples input so that I could find this and I hope vainly it might help someone else.

Bruce
If you create a standard Delphi thread, then it is not necessary to set this value. But sometimes, like in a DLL where you are being called multi-threaded, you have to set it yourself. Good spot.
mj2008
Helped me, cheers!
Blorgbeard
A: 

You found your answer - IsMultiThread. This has to be used anytime to revert to using the API and create threads. From MSDN: CreateTimerQueueTimer is creating a thread pool to handle this functionality so you have an outside thread working with the main VCL thread with no protection. (Note: your CS.acquire/release doesn't do anything at all unless other parts of the code respect this lock.)

Darian Miller
It's not quite clear to me why "CS.acquire/release doesn't do anything" as I would have thought it would protect that code FormatDateTime in both places, but I suppose the Main VCL is using the memory manager somewhere else that I can't easily control and causing the problem.
Bruce
A: 

Re. your last question about Winsock and overlapping I/O: You should look closely at Indy.

Indy uses blocking I/O, and is a great choice when you want high performance network IO regardless of what the main thread is doing. Now you've cracked the multi-threading issue, you should just create another thread (or more) to use indy to handle your I/O.

Roddy
A: 

The problem with Indy is that if you need many connections it's not effiecient at all. It requires one thread per connection (blocking I/O) which doesn't scale at all, hence the benefit of IOCP and Overlapping IO, it's pretty much the only scalable way on Windows.

Bruce
Sure, but it's usually such a PITA I'd consider it premature optimization in many cases. This thread may have some pointers for you, though. http://stackoverflow.com/questions/37185/
Roddy
A: 

For update2 :

There is a free IOCP socket components : http://www.torry.net/authorsmore.php?id=7131 (source code included)

"By Naberegnyh Sergey N.. High performance socket server based on Windows Completion Port and with using Windows Socket Extensions. IPv6 supported. "

i've found it while looking better components/library to rearchitecture my little instant messaging server. I haven't tried it yet but it looks good coded as a first impression.

AhmetC