mikerabat 20 Posted April 25, 2022 Hi all! In our system we load a bunch of files on startup in parallel so for each of these files we craete a thread that does that in the background. basically this is what how it is done: TEVentChannel is the data object that holds the data -> on the data object creation a thread (TEvtLoadThread = class(TThread)) is created which does the loading -> of course it is ensured that data access and loading is mutal exclusive. The snippset of code that is involved is: constructor TEventChannel.TEvtLoadThread.Create(parent: TEventChannel); begin fParent := parent; inherited Create(False); end; procedure TEventChannel.TEvtLoadThread.Execute; begin NameThreadForDebugging('Load '+AnsiString(fParent.ChanName)); // ########################################### // #### Just start loading the data try fParent.Reload; except on E : EChannelException do begin fParent.fLoadFailed := False; fParent.fDataLoaded := lsBadRecId; end; else // damaged file?? fParent.fDataLoaded := lsAll; fParent.Clear; fParent.fLoadFailed := True; end; end; My problem here is that on a system on which that code ran for over 2 years without problems now happens to fail with the error "Cannot call Start on a running or suspended thread" This error is issued in the TThread.Afterconstruction call and it seems that our system fails to create that thread... Looking at the TThread code it is most likely that the Thread could not be created so what on earth could be could interfere here? We tried to disable FSecure (or at least they claim no security system is monitoring our program) and Windows Defender without any change. Is there anything known out there that could limit an external program in such way? kind regards Mike Share this post Link to post
Anders Melander 1782 Posted April 25, 2022 Create the thread suspended and then start it once the constructor returns. It is generally a bad idea to construct a thread with CreateSuspended=False. I.e. instead of: constructor TEventChannel.TEvtLoadThread.Create(parent: TEventChannel); begin fParent := parent; inherited Create(False); end; do this: constructor TEventChannel.TEvtLoadThread.Create(parent: TEventChannel); begin inherited Create(True); fParent := parent; end; ... TheThread := TEvtLoadThread.Create(Something); TheThread.Start; ... Share this post Link to post
Remy Lebeau 1392 Posted April 25, 2022 (edited) 3 hours ago, mikerabat said: The snippset of code that is involved is: Offhand, the code looks fine to me. I've never had any problems with using TThread with CreateSuspended=False. However, as a workaround, you can try creating the TThread with CreateSuspended=True, and then manually call TThread.Start() after the constructor has exited. If that works, then I would suspect that Embarcdero may have possibly introduced a regression in TThread's construction. What version of Delphi are you using? Quote My problem here is that on a system on which that code ran for over 2 years without problems now happens to fail with the error "Cannot call Start on a running or suspended thread" This error is issued in the TThread.Afterconstruction call and it seems that our system fails to create that thread... On Windows, the TThread constructor creates a thread with the CREATE_SUSPENDED flag enabled, then the TThread.AfterConstruction() method calls ResumeThread() if the TThread constructor was called with CreateSuspended=False (as is the case in your example). The error message you are seeing is raised if ResumeThread() fails to resume the thread, ie either because the API failed outright (ResumeThread() returns -1), or the thread was suspended multiple times (ResumeThread() returns > 1), neither of which should be happening in your example. Are you, by chance, calling TThread.Start() anywhere else in your code? If so, you should not be calling Start() on a TThread created with CreateSuspended=False. Quote Looking at the TThread code it is most likely that the Thread could not be created If that were the case, you would be getting an exception from the TThread constructor, not its AfterConstruction() method. Edited April 25, 2022 by Remy Lebeau Share this post Link to post
Remy Lebeau 1392 Posted April 25, 2022 4 minutes ago, Anders Melander said: Create the thread suspended and then start it once the constructor returns. It is generally a bad idea to construct a thread with CreateSuspended=False. In general, there is nothing wrong with creating a TThread with CreateSuspended=False. That has worked just fine for the past 2 decades. So, unless Embarcadero has recently broken TThread, something else is likely going on. 2 Share this post Link to post
Anders Melander 1782 Posted April 25, 2022 9 minutes ago, Remy Lebeau said: That has worked just fine for the past 2 decades. You're right. I need to flush my long term memory of obsolete information 🙂. In Delphi 5 the tread was started immediately in the TThread constructor. Since Delphi 6 it is started in AfterConstruction. 1 Share this post Link to post
mikerabat 20 Posted April 25, 2022 Thanks Remy!! Right... the call to BeginThread actually suceeds - the callstack clearly indicates that it's the ResumeThread call that fails. So... the only thing I found online was that there is a right " THREAD_SUSPEND_RESUME " which may have some influence here... But I have absolutely no idea what could change that. Note that the code actually works on around 8k of installations but there are only a few (2 for now) that seem to make trouble in that regard. The thing is that if there are way fewer files to be loaded (e.g. 6-8) it works most of the time (aka not always)... Also the system worked for 2 years now without any problems... so I'm quite stunned that now something pops up. So... Is there any 3rd party or any configurable restrictions on Windows that disallow something like releasing many threads at once? kind regards Mike Share this post Link to post
Remy Lebeau 1392 Posted April 25, 2022 (edited) Quote So... the only thing I found online was that there is a right " THREAD_SUSPEND_RESUME " which may have some influence here... But I have absolutely no idea what could change that. Not relevant in this case, because the handle returned by CreateThread() has THREAD_ALL_ACCESS permissions, which includes THREAD_SUSPEND_RESUME. That permission is meant to prevent other people from suspending/resuming threads they don't have access to. You have full access to your own threads. Quote Note that the code actually works on around 8k of installations but there are only a few (2 for now) that seem to make trouble in that regard. You are just going to have to debug your app on these failing systems to see what ResumeThread() is actually returning: -1 = failure, what does GetLastError() report? 0 = was not suspended to begin with 1 = was suspended, now resumed (this is what you want in this situation) > 1 = was and still is suspended Quote So... Is there any 3rd party or any configurable restrictions on Windows that disallow something like releasing many threads at once? Not that I'm aware of. The number of threads you can create is limited only by available memory. Even if there were a configurable limit, you would be getting failures in CreateThread(), not in ResumeThread(). Edited April 25, 2022 by Remy Lebeau Share this post Link to post
mikerabat 20 Posted May 2, 2022 Thanks for all the input. Finally it turned out that a white listing software from https://seculution.de/ was the problem at all. They thought we did some bad stuff and their heuristics failed miserably. Please note that they also tried to just disable that software to rule them out but disabling was not enough ☹️ Share this post Link to post