Jump to content
pyscripter

TThread always raises OS Errror

Recommended Posts

Posted (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 by pyscripter

Share this post


Link to post

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.

 

  • Like 1
  • Thanks 1

Share this post


Link to post
Posted (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 by pyscripter

Share this post


Link to post
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.

  • Like 3
  • Thanks 1

Share this post


Link to post
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

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
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.

  • Like 1

Share this post


Link to post

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.

  • Like 3

Share this post


Link to post
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.

😄

  • Like 1

Share this post


Link to post
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.

 

  • Like 3

Share this post


Link to post

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:

  1. Was this a known fact?
  2. 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
Posted (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:

  1. Was this a known fact?
  2. 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 by pyscripter

Share this post


Link to post

Find out by debugging. Set a breakpoint on SetLastError. And inspect the call stack when it triggers. 

Share this post


Link to post
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:

  1. It doesn't matter and
  2. It doesn't matter

Share this post


Link to post
Posted (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:

  1. Was this a known fact?
  2. 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 by Remy Lebeau

Share this post


Link to post

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

 

  • Like 1

Share this post


Link to post
Posted (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 by Attila Kovacs

Share this post


Link to post
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

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
Posted (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 by Kas Ob.

Share this post


Link to post
Posted (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 by Remy Lebeau

Share this post


Link to post
Posted (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 by Remy Lebeau

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×