I've been struggling for the last couple of days with a bug I can't understand. This only happens in a Windows 2012 server (64bits), while it does not happen (at least) on the following windows versions: Windows XP (32 bits), Windows 7 (32 and 64bits), Windows 8 (64bits), Windows 8.1 (64 bits), Windows 10 (64 bits), Windows 2003 Server (32 bits).
Please note that the application in all cases is a 32 bit binary.
When the application starts it initializes some stuff, and in the end it creates a series of threads, all of them in suspended mode so that I can manually start them later.
In the following code, one would expect t to be started after the sleep. However, by the time I tried to explicitly call Start on the thread, I realized that it had already been started (somehow), leading to a thread already started exception (which would be ok considering that the thread had already been started).
// some initialization code is run before this, but nothing directly related
procedure foo;
var
t : TThread; // note that there can be no reference to this thread from the outside since it's a local var
begin
t := TMyThread.Create(true); // TMyThread
sleep(3000); // time window of 3 seconds where the thread shouldn't have started
t.Start; // now i want to start it manually, but the thread has already been started soon after it has been created
end;
The same happens also with regular (dummy) anonymous threads, so it wouldn't be TMyThread's fault:
// some initialization code is run before this, but nothing directly related
procedure foo;
var
t : TThread; // note that there can be no reference to this thread from the outside since it's a local var
begin
t := TThread.CreateAnonymousThread( // to avoid using my possibly faulted TMyThread class, I'm now testing with a regular anonymous thread and the problem persists
procedure begin
log('Hi from anon thread');
sleep(5000); // to make it live 5 seconds for testing purposes
end);
sleep(3000); // time window of 3 seconds where the thread shouldn't have started
t.Start; // now i want to start it manually, but the thread has already been started soon after it has been created
end;
Is there some mechanism present in Windows 2012 server regarding thread management that I'm missing?
LOG EXCERPTS:
The following is an excerpt from a healthy log file. You can see how the start method calls the startThreads method which in turns creates the first of the threads. The next lines represent the calls made from the CMP_AbstractThread's constructor. evInitialized and evStarted are TEvents created in CMP_AbstractThread's constructor.
12/03/2016 20:28:47.336: LOG_DEBUG @ PID=5576 ThreadID=5188 (TExternalThread) @ CMPPS_Application.start: start ...
12/03/2016 20:28:47.336: LOG_DEBUG @ PID=5576 ThreadID=5188 (TExternalThread) @ CMPPS_Application.createThreads: createThreads ...
12/03/2016 20:28:47.352: LOG_FINEST @ PID=5576 ThreadID=5188 (TExternalThread) @ CMP_AbstractThread.: (PID=5576 ThreadID=0 (CMPP_EventsThread)).Create ...
12/03/2016 20:28:47.352: LOG_FINEST @ PID=5576 ThreadID=5188 (TExternalThread) @ CMP_AbstractThread.: (PID=5576 ThreadID=6084 (CMPP_EventsThread)).Create.
12/03/2016 20:28:47.352: LOG_DEBUG @ PID=5576 ThreadID=5188 (TExternalThread) @ CMP_AbstractThread.: (PID=5576 ThreadID=6084 (CMPP_EventsThread).evInitialized).Create ...
12/03/2016 20:28:47.352: LOG_DEBUG @ PID=5576 ThreadID=5188 (TExternalThread) @ CMP_AbstractThread.: (PID=5576 ThreadID=6084 (CMPP_EventsThread).evInitialized).Create.
12/03/2016 20:28:47.352: LOG_DEBUG @ PID=5576 ThreadID=5188 (TExternalThread) @ CMP_AbstractThread.: (PID=5576 ThreadID=6084 (CMPP_EventsThread).evInitialized).resetEvent ...
12/03/2016 20:28:47.352: LOG_DEBUG @ PID=5576 ThreadID=5188 (TExternalThread) @ CMP_AbstractThread.: (PID=5576 ThreadID=6084 (CMPP_EventsThread).evInitialized).resetEvent.
12/03/2016 20:28:47.352: LOG_DEBUG @ PID=5576 ThreadID=5188 (TExternalThread) @ CMP_AbstractThread.: (PID=5576 ThreadID=6084 (CMPP_EventsThread).evStarted).Create ...
12/03/2016 20:28:47.352: LOG_DEBUG @ PID=5576 ThreadID=5188 (TExternalThread) @ CMP_AbstractThread.: (PID=5576 ThreadID=6084 (CMPP_EventsThread).evStarted).Create.
12/03/2016 20:28:47.352: LOG_DEBUG @ PID=5576 ThreadID=5188 (TExternalThread) @ CMP_AbstractThread.: (PID=5576 ThreadID=6084 (CMPP_EventsThread).evStarted).resetEvent ...
12/03/2016 20:28:47.352: LOG_DEBUG @ PID=5576 ThreadID=5188 (TExternalThread) @ CMP_AbstractThread.: (PID=5576 ThreadID=6084 (CMPP_EventsThread).evStarted).resetEvent.
The following is an excerpt from a Windows 2012 session. You can see that the thread starts even before its own constructor has finished. The log lines coming from the threads Execute method are coming from thread 5864, the one that is still being created. In this specific case, the anomaly didn't even wait for the constructor to finish (it usually does). It would seem as if the TThread was created with False instead of True.
12/03/2016 20:29:31.813: LOG_DEBUG @ PID=3796 ThreadID=4460 (TExternalThread) @ CMPPS_Application.start: start ...
12/03/2016 20:29:31.813: LOG_DEBUG @ PID=3796 ThreadID=4460 (TExternalThread) @ CMPPS_Application.createThreads: createThreads ...
12/03/2016 20:29:31.813: LOG_FINEST @ PID=3796 ThreadID=4460 (TExternalThread) @ CMP_AbstractThread.: (PID=3796 ThreadID=0 (CMPP_EventsThread)).Create ...
12/03/2016 20:29:31.813: LOG_FINEST @ PID=3796 ThreadID=4460 (TExternalThread) @ CMP_AbstractThread.: (PID=3796 ThreadID=5864 (CMPP_EventsThread)).Create.
12/03/2016 20:29:31.813: LOG_DEBUG @ PID=3796 ThreadID=4460 (TExternalThread) @ CMP_AbstractThread.: (PID=3796 ThreadID=5864 (CMPP_EventsThread).evInitialized).Create ...
12/03/2016 20:29:31.813: LOG_DEBUG @ PID=3796 ThreadID=4460 (TExternalThread) @ CMP_AbstractThread.: (PID=3796 ThreadID=5864 (CMPP_EventsThread).evInitialized).Create.
12/03/2016 20:29:31.813: LOG_DEBUG @ PID=3796 ThreadID=4460 (TExternalThread) @ CMP_AbstractThread.: (PID=3796 ThreadID=5864 (CMPP_EventsThread).evInitialized).resetEvent ...
12/03/2016 20:29:31.813: LOG_DEBUG @ PID=3796 ThreadID=4460 (TExternalThread) @ CMP_AbstractThread.: (PID=3796 ThreadID=5864 (CMPP_EventsThread).evInitialized).resetEvent.
12/03/2016 20:29:31.813: LOG_FINEST @ PID=3796 ThreadID=5864 (CMPP_EventsThread) @ CMP_AbstractThread.Execute: (PID=3796 ThreadID=5864 (CMPP_EventsThread)) Executed.
12/03/2016 20:29:31.813: LOG_NORMAL @ PID=3796 ThreadID=5864 (CMPP_EventsThread) @ CMP_AbstractThread.Execute: (PID=3796 ThreadID=5864 (CMPP_EventsThread)) Down.
12/03/2016 20:29:31.828: LOG_DEBUG @ PID=3796 ThreadID=4460 (TExternalThread) @ CMP_AbstractThread.: (PID=3796 ThreadID=5864 (CMPP_EventsThread).evStarted).Create ...
12/03/2016 20:29:31.828: LOG_DEBUG @ PID=3796 ThreadID=4460 (TExternalThread) @ CMP_AbstractThread.: (PID=3796 ThreadID=5864 (CMPP_EventsThread).evStarted).Create.
12/03/2016 20:29:31.828: LOG_DEBUG @ PID=3796 ThreadID=4460 (TExternalThread) @ CMP_AbstractThread.: (PID=3796 ThreadID=5864 (CMPP_EventsThread).evStarted).resetEvent ...
12/03/2016 20:29:31.828: LOG_DEBUG @ PID=3796 ThreadID=4460 (TExternalThread) @ CMP_AbstractThread.: (PID=3796 ThreadID=5864 (CMPP_EventsThread).evStarted).resetEvent.
CODE EXCERPTS:
constructor CMP_AbstractThread.Create(suspended : boolean);
begin
inherited Create(suspended); // this one sets up threadName
FEvInitialized := CMP_Event.Create(threadName, 'evInitialized');
FEvInitialized.ResetEvent;
FEvStarted := CMP_Event.Create(threadName, 'evStarted');
FEvStarted.ResetEvent;
end;
Constructor CMP_Thread.Create(suspended : boolean);
Begin
log(LOG_FINEST, format('(%s).Create ...', [ThreadInfo]));
inherited Create(suspended);
FThreadName := threadInfo;
log(LOG_FINEST, format('(%s).Create.', [ThreadName]));
End;
procedure CMPPS_Application.createThreads;
begin
logGuard('createThreads', procedure begin
FEventsThread := CMPP_EventsThread(CMPP_EventsThread.Create(true)
.setDbConnection(FDB)
.setLogger(GEventsLogger)
.setDelay(MP_EVENTS_THREAD_DELAY));
end);
end;
CMPP_EventsThread = class(CMPC_EventsThread)
// all the following methods are inherited from different layers
// Create(boolean) is inherited directly from CMP_AbstractThread
// setDbConnection(IMP_DBConnection)
// setLogger(IMP_Logger)
// setDelay(integer)
end;