pyscripter 689 Posted May 12, 2020 (edited) The following code program ThreadOSError; {$APPTYPE CONSOLE} uses System.SysUtils, System.Classes; begin Assert(GetLastError=0); TThread.CreateAnonymousThread(procedure begin try CheckOSError(GetLastError); except On E: Exception do WriteLn(E.Message); end; end).Start; ReadLn; end. produces the following output in 10.3,3 in Windows. System Error. Code: 87. The parameter is incorrect Same is true whichever way you run Thread code. Is this a known issue? Any idea why the OS error is raised? Edited May 12, 2020 by pyscripter Share this post Link to post
Der schöne Günther 316 Posted May 12, 2020 This has nothing to do with threads. You're using GetLastError() altough there was no error for you to care about. https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror Quote Most functions that set the thread's last-error code set it when they fail. However, some functions also set the last-error code when they succeed. If the function is not documented to set the last-error code, the value returned by this function is simply the most recent last-error code to have been set; some functions set the last-error code to 0 on success and others do not. 1 1 Share this post Link to post
pyscripter 689 Posted May 12, 2020 (edited) @Der schöne GüntherI am not sure about the relevance of your quote. No matter what your thread code is or how you run it (by inheriting from TThread, creating an anonymous thread or whatever), something results in an OS error 87 (it is always the same code) that corresponds to "parameter is incorrect". This OS error has been raised before your thread code has started running. And this has nothing to do with the return code of a thread. This is a problem because if you do any OS stuff in your thread code, and then you want to check whether an error was raised using CheckOSError, an exception will be raised. A workaround would be to always start your thread code with the statement: SetLastError(0); Edited May 12, 2020 by pyscripter Share this post Link to post
Anders Melander 1782 Posted May 12, 2020 3 hours ago, Der schöne Günther said: This has nothing to do with threads. You're using GetLastError() altough there was no error for you to care about. I agree. The test case is invalid. 1 hour ago, pyscripter said: This is a problem because if you do any OS stuff in your thread code, and then you want to check whether an error was raised using CheckOSError, an exception will be raised. Nope. There's no API contract that promises you that GetLastError will or should be zero at the point where you are testing it. If it's important to you that it is zero then sure, go ahead and set it to zero but it would be better to not misuse GetLastError that way. You should test GetLastError right after you have made an Win API call because at that point it will have been set to a relevant value. 3 1 Share this post Link to post
Lars Fosdal 1792 Posted May 12, 2020 This is why Anders' advice is good advice: https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-setlasterror Quote Most functions call SetLastError or SetLastErrorEx only when they fail. 1 Share this post Link to post
pyscripter 689 Posted May 12, 2020 (edited) @Anders Melander @Lars FosdalSo you do not think this OS error is the result of Delphi making a call to an OS (Windows) function with invalid parameters. Edited May 12, 2020 by pyscripter Share this post Link to post
Anders Melander 1782 Posted May 12, 2020 1 minute ago, pyscripter said: So you do not think this OS error is the result of Delphi making a call to an OS (Windows) function with invalid parameters? Probably not. The interpretation would depend on the API function that set the status value (notice I'm not calling it an error code). For example it could mean that a supplied parameter value didn't apply to the current context. As long as the caller handles the condition somehow then everything is fine. Share this post Link to post
Lars Fosdal 1792 Posted May 12, 2020 That depends. I don't know if the LastError thread var is cleared on a new thread, or if you must do it yourself. SetLastError at thread start will at least ensure it is cleared, but without knowing the code being run, it is impossible to assess if there is any point in doing a "blind" GetLastError. If there are multiple calls to Windows functions in that code, what value is there in only showing the last one? Was the error situation handled without LastError being cleared? Is it then really an error anymore? Share this post Link to post
Anders Melander 1782 Posted May 12, 2020 4 minutes ago, Lars Fosdal said: I don't know if the LastError thread var is cleared on a new thread, or if you must do it yourself. AFAIK it isn't and there's no reason for you to do it yourself because the value is irrelevant unless you're testing the result of an API function that has indicated that GetLastError should be used to get information about a failure. Here's another example of the exact same mistake: CreateThread() // GetLastError() returns 87 9 minutes ago, Lars Fosdal said: Was the error situation handled without LastError being cleared? You don't need to "clear LastError" unless your function is using GetLastError as it's own status reporting mechanism and that would be very rare. 1 Share this post Link to post
David Heffernan 2345 Posted May 12, 2020 The only error here is the code that calls GetLastError and uses the value at a time when its return value is meaningless. All you have to do is to stop doing that. 3 Share this post Link to post
Sherlock 663 Posted May 12, 2020 3 minutes ago, David Heffernan said: The only error here is the code that calls GetLastError and uses the value at a time when its return value is meaningless. All you have to do is to stop doing that. Patient: Doctor, my eye hurts, when I poke it. Doc: Well then don't poke it. 😄 1 Share this post Link to post
Remy Lebeau 1393 Posted May 12, 2020 11 hours ago, pyscripter said: The following code ... produces the following output in 10.3,3 in Windows. System Error. Code: 87. The parameter is incorrect Same is true whichever way you run Thread code. Is this a known issue? Any idea why the OS error is raised? (Get|Set)LastError() use thread-local storage internally. The last-error code is stored inside the OS thread object itself. It doesn't matter how you create the thread, calling SetLastError() in one thread context and then calling GetLastError() in another thread context will NEVER work. The last-error simply can't cross thread boundaries. This is documented behavior: https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror Quote Retrieves the calling thread's last-error code value. The last-error code is maintained on a per-thread basis. Multiple threads do not overwrite each other's last-error code. https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-setlasterror Quote Sets the last-error code for the calling thread. ... The last-error code is kept in thread local storage so that multiple threads do not overwrite each other's values. https://docs.microsoft.com/en-us/windows/win32/debug/last-error-code Quote This error code is maintained separately for each running thread; an error in one thread does not overwrite the last-error code in another thread. 3 Share this post Link to post
pyscripter 689 Posted May 12, 2020 Let me thank again everyone that responded. You said that the error-code is thread specific, this is not the way to check whether a specific API call failed etc., things I fully agree with, but which I knew already. My post just made an observation. Whenever you run code in a thread, GetLastError always returns 87 which corresponds to a call with invalid parameters, The original post asked two questions: Was this a known fact? More importantly why? In other words, what is the API call that sets this error? Even if it is inconsequential, and I believe it is, I had the curiosity to find out. I don't think I got an answer to these questions. Share this post Link to post
pyscripter 689 Posted May 12, 2020 (edited) 9 minutes ago, pyscripter said: Let me thank again everyone that responded. You said that the error-code is thread specific, this is not the way to check whether a specific API call failed etc., things I fully agree with, but which I knew already. My post just made an observation, probably not of any significance. Whenever you run code in a thread, GetLastError always returns 87 which corresponds to a call with invalid parameters, The original post asked two questions: Was this a known fact? More importantly why? In other words, what is the API call in the TThread code, that sets this error? Even if it is inconsequential, and I believe it is, I had the curiosity to find out. I don't think I got an answer to these questions. Edited May 12, 2020 by pyscripter Share this post Link to post
David Heffernan 2345 Posted May 12, 2020 Find out by debugging. Set a breakpoint on SetLastError. And inspect the call stack when it triggers. Share this post Link to post
Anders Melander 1782 Posted May 12, 2020 1 hour ago, pyscripter said: I don't think I got an answer to these questions. I think you did. Since the value of GetLastError is meaningless the way you used it: It doesn't matter and It doesn't matter Share this post Link to post
Remy Lebeau 1393 Posted May 12, 2020 (edited) 1 hour ago, pyscripter said: My post just made an observation. Whenever you run code in a thread, GetLastError always returns 87 which corresponds to a call with invalid parameters, The original post asked two questions: Was this a known fact? More importantly why? In other words, what is the API call that sets this error? Even if it is inconsequential, and I believe it is, I had the curiosity to find out. Why does it matter? In your example, the error code would have to be getting set before your thread procedure is even called, in which case it would be internal to the OS when preparing the thread, or even internal to the RTL before calling TAnonymousThread.Execute(), but either way it is not in your own code, so who cares about WHY it happens? You are not supposed to be using that error to begin with, since it has no meaningful value in the context you are using it in. Its value is indeterminate in that context. In fact, I just did a quick test of my own, and the value of GetLastError() upon entering the thread procedure WAS NOT 87, it was 0. So the result you are observing has to be environmental in nature. Edited May 12, 2020 by Remy Lebeau Share this post Link to post
Darian Miller 361 Posted May 12, 2020 If you put a breakpoint on the first line of code in function ThreadProc in System.Classes and call GetLastError, it will be 87. This is the start of the thread code in Delphi so it's apparently simply starting out that way every time (at least in my limited testing.) As far "was this a known fact"... not known to me, not known to the few MS document searches that I did, but it does show up on a question from 2011 on StackOverflow: https://stackoverflow.com/questions/7199139/createthread-getlasterror-returns-87 1 Share this post Link to post
Attila Kovacs 629 Posted May 12, 2020 (edited) I understand his curiosity. It's only matters if there was an error which was not handled but it should have been. In 10.1 and 10.2 (the last release I have) the above example does not yield any error, GetLastError returns 0. You have to step through the app and look where it happens if you wan't to know. "Is this a known issue?" As others has also mentioned, it's not necessarily an issue, but a state. Edited May 12, 2020 by Attila Kovacs Share this post Link to post
Darian Miller 361 Posted May 12, 2020 10 minutes ago, Remy Lebeau said: Its value is indeterminate in that context. That's probably the best answer to #2. If you don't know the API methods being called right before you call GetLastError, then it doesn't matter what GetLastError is. The best answer to #1 is Yes, it's a known fact that GetLastError is only meaningful in specific situations. "You should call the GetLastError function immediately when a function's return value indicates that such a call will return useful data" https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror Share this post Link to post
Guest Posted May 12, 2020 I think it does matter, as i don't see this error on XE8 or Seattle, then might be something chanaged in 10.3.3 As for it is known from this question https://stackoverflow.com/questions/7199139/createthread-getlasterror-returns-87 , then read the first comment on the question which make sense and make this relevant and matter, Is this a bug in Windows, the Delphi RTL, or not a bug, in last case it should be documented or fixed in the RTL with SetLastError to 0 before resuming the execution of user code. On side note : I also observed this behaviour when GetOverlappedResult return True but checking the last error it is the same "invalid parameter", this does happen in rare situations when GetOverlappedResult used with sockets and after hours with many connection and under heavy load, switching to WSAGetOverlappedResult make this disappear and i never saw it after that. the documentation of GetOverlappedResult doesn't mention the usage with sockets per se , https://docs.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-getoverlappedresult , while https://support.microsoft.com/en-us/help/181611/socket-overlapped-i-o-versus-blocking-nonblocking-mode making it clear it is fine to be used with them. I don't have 10.3.3 but at least do you confirm pyscripter observation ? if so then this should be tracked just in case something critical is broken with anonymous threads. Share this post Link to post
Guest Posted May 12, 2020 (edited) Ok , now i don't how to translate this into facts. Quote /* Test how passing NULL as a pointer to threadid works */ SetLastError(0xFACEaBAD); thread[0] = CreateThread(NULL,0,threadFunc2,NULL,0,&tid); GLE = GetLastError(); if (thread[0]) { /* NT */ ok(GLE==0xFACEaBAD, "CreateThread set last error to %d, expected 4207848365\n", GLE); ret = WaitForSingleObject(thread[0],100); ok(ret==WAIT_OBJECT_0, "threadFunc2 did not exit during 100 ms\n"); ret = GetExitCodeThread(thread[0],&exitCode); ok(ret!=0, "GetExitCodeThread returned %d (expected nonzero)\n", ret); ok(exitCode==99, "threadFunc2 exited with code: %d (expected 99)\n", exitCode); ok(CloseHandle(thread[0])!=0,"Error closing thread handle\n"); } else { /* 9x */ ok(GLE==ERROR_INVALID_PARAMETER, "CreateThread set last error to %d, expected 87\n", GLE); } https://github.com/wine-mirror/wine/blob/master/dlls/kernel32/tests/thread.c#L509 Edited May 12, 2020 by Guest Share this post Link to post
Remy Lebeau 1393 Posted May 13, 2020 (edited) 2 hours ago, Kas Ob. said: I think it does matter, as i don't see this error on XE8 or Seattle, then might be something chanaged in 10.3.3 But, if you decide to use GetLastError() the way it is MEANT to be used, you would never see this happen AT ALL, because you would NOT be calling GetLastError() until after an API function actually overwrites the calling thread's last-error with a meaningful value first. Quote As for it is known from this question https://stackoverflow.com/questions/7199139/createthread-getlasterror-returns-87 , then read the first comment on the question which make sense and make this relevant and matter, That user's mistake (using a thread procedure of the wrong signature) has no bearing on how GetLastError() works. If anything, all that mistake would do is cause an extra value to be pushed on the call stack before entry to the thread procedure (LPVOID lpParameter), and that value would not be popped off the call stack properly after exiting the procedure. But inside the procedure, there is no effect. Quote Is this a bug in Windows, the Delphi RTL, or not a bug I don't think it is anyone's bug, except maybe yours if you choose to misuse GetLastError() in the first place. Quote in last case it should be documented or fixed in the RTL with SetLastError to 0 before resuming the execution of user code. That is absolutely not needed when using GetLastError() the proper way. Quote I also observed this behaviour when GetOverlappedResult return True but checking the last error it is the same "invalid parameter", this does happen in rare situations when GetOverlappedResult used with sockets and after hours with many connection and under heavy load, switching to WSAGetOverlappedResult make this disappear and i never saw it after that. GetLastError() simply does not return a meaningful value when GetOverlappedResult() returns TRUE, only when it returns FALSE. As is the case with most API functions. Most API functions DO NOT overwrite the calling thread's last-error on success, only on failure, unless explicitly documented otherwise (for example, CreateEvent(), etc). Or, if they do overwrite the last-error on success, it could just be as a side effect of internal operations that were handled as needed, and you are supposed to ignore the last-error since the API returned success to you. Quote the documentation of GetOverlappedResult doesn't mention the usage with sockets per se , https://docs.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-getoverlappedresult , while https://support.microsoft.com/en-us/help/181611/socket-overlapped-i-o-versus-blocking-nonblocking-mode making it clear it is fine to be used with them. Socket Overlapped I/O operations use the WSAOVERLAPPED struct and WSAGetOverlappedResult() function. Which just HAPPEN to currently be binary compatible with OVERLAPPED and GetOverlappedResult() as an implementation detail. Don't rely on that. Use the proper API. Quote I don't have 10.3.3 but at least do you confirm pyscripter observation ? I do not have 10.3.3, but I cannot reproduce or confirm it in other versions. Quote if so then this should be tracked just in case something critical is broken with anonymous threads. There is no difference between an anonymous thread and any other TThread. TThread.CreateAnonymousThread() merely creates a TAnonymousThread object, a TThread descendant whose Execute() simply calls the provided user procedure, no frills, no other logic: type TAnonymousThread = class(TThread) private FProc: TProc; protected procedure Execute; override; public constructor Create(const AProc: TProc); end; constructor TAnonymousThread.Create(const AProc: TProc); begin inherited Create(True); FreeOnTerminate := True; FProc := AProc; end; procedure TAnonymousThread.Execute; begin FProc(); end; class function TThread.CreateAnonymousThread(const ThreadProc: TProc): TThread; begin Result := TAnonymousThread.Create(ThreadProc); end; So, any issues with GetLastError() used in TThread.CreateAnonymousThread() would also have to affect all TThread objects in general. Which would imply a problem is present in Classes.ThreadProc() itself before TThread.Execute() is called. And that is not likely given how little code is present before that call. On Windows, there are only 2 lines of code executed before Execute() is called (unless something has changed in recent RTL versions): the assignment of TThread.FCurrentThread (a threadvar), and the opening of a try..finally block. Exception handling blocks do not affect a thread's last-error, but TLS storage can. But it is unlikely that the assignment of FCurrentThread would fail, unless the RTL were not initialized properly at process startup. Edited May 13, 2020 by Remy Lebeau Share this post Link to post
Remy Lebeau 1393 Posted May 13, 2020 (edited) 2 hours ago, Kas Ob. said: /* Test how passing NULL as a pointer to threadid works */ SetLastError(0xFACEaBAD); thread[0] = CreateThread(NULL,0,threadFunc2,NULL,0,&tid); GLE = GetLastError(); Um, maybe I'm missing something, but that test is not passing a NULL pointer for the threadid. But, it doesn't matter anyway, because that test code belongs to WINE's emulated implementation of the Win32 API, it does not belong to the actual Win32 API on Windows. Edited May 13, 2020 by Remy Lebeau Share this post Link to post
David Heffernan 2345 Posted May 13, 2020 7 hours ago, Kas Ob. said: I think it does matter You think wrong Share this post Link to post