Recent

Author Topic: Threads and Kernel.dll  (Read 3967 times)

torbente

  • Sr. Member
  • ****
  • Posts: 325
    • Noso Main Page
Threads and Kernel.dll
« on: July 17, 2018, 07:36:53 pm »
Using windows 8.1 - Lazarus 1.8.4
From this project:
https://forum.lazarus.freepascal.org/index.php/topic,41618.0.html

Almost all the project is single thread. But a new thread is called to run the PoW if the user wants:

Code: Pascal  [Select][+][-]
  1. if ((OptionsData.Mining) and (not MINER_IsMinerOn) and (STATUS_Updated) and (not MINER_BlockFound) and (MINER_TargetHash = copy(LOCAL_LastBlockHash,1,MINER_TargetChars))) then
  2.    begin
  3.    OutputText('Mining. Target: '+MINER_TargetHash);
  4.    MINER_IsMinerOn := true;
  5.    Beginthread(tthreadfunc(@RunMiner));
  6.    end  

This is the function:
Code: Pascal  [Select][+][-]
  1. function RunMiner():String;
  2. var
  3.   HashValue : String = '';
  4. begin
  5. MINER_ResultHash := '';
  6. if MINER_HashCounter = 2147483647 then
  7.    begin
  8.    MINER_HashCounter := 0;
  9.    MINER_HashSeed := GetHashSeed();
  10.    end;
  11. while ((MINER_HashCounter < 2147483647) and (OptionsData.Mining) and (STATUS_Updated) and (MINER_IsMinerOn)) do
  12.    begin
  13.    HashValue := HashMD5String(MINER_HashSeed+IntToStr(MINER_HashCounter));
  14.    if AnsiContainsStr(HashValue,MINER_TargetHash) then
  15.       begin
  16.       MINER_ResultHash := MINER_HashSeed+IntToStr(MINER_HashCounter);
  17.       OutputText('Hash Found for '+MINER_TargetHash+' : '+MINER_ResultHash);
  18.       MINER_HashSeed := GetHashSeed();
  19.       MINER_HashCounter := 0;
  20.       MINER_BlockFound := true;
  21.       break;
  22.       end;
  23.    MINER_HashCounter := MINER_HashCounter+1;
  24.    end;
  25. MINER_IsMinerOn := false;
  26. OutputText('Miner is OFF');
  27. end;

OptionsData.Mining, STATUS_Updated and MINER_IsMinerOn are global variables, so changing this values will end the function (and the thread).

Works well most of time, but sometimes the program hangs and windows closes it reporting a kernel.dll error. If the miner is not started, the program never had that issue (even managing multiple indy TCP connections).

Maybe i need a safer way to call the new thread?

The projects is open source, so feel free to check it.

NOTE: Outputtext do not modify the visual componnets. (forbidden in threads)
This is the function:
Code: Pascal  [Select][+][-]
  1. Procedure Outputtext(TextToShow:String;showhour:boolean=true);
  2. Begin
  3. if showhour then texttoshow := timetostr(now)+' '+TextToShow;
  4. MainMemoStrings.Add(TextToShow);
  5. End;  
« Last Edit: July 17, 2018, 07:43:57 pm by torbente »
Noso Cryptocurrency Main Developer
https://github.com/DevTeamNoso/NosoWallet

PascalDragon

  • Hero Member
  • *****
  • Posts: 5444
  • Compiler Developer
Re: Threads and Kernel.dll
« Reply #1 on: July 17, 2018, 09:13:12 pm »
Code: Pascal  [Select][+][-]
  1. Beginthread(tthreadfunc(@RunMiner));

Code: Pascal  [Select][+][-]
  1. function RunMiner():String;

Why do you think it is a good idea to cast a function with the signature "function: String" to a procedure variable of type TThreadFunc that expects the signature "function(Pointer): PtrInt"? Change your thread function to the correct signature and remove the cast. This might have been one source of problems.  ;D

NOTE: Outputtext do not modify the visual componnets. (forbidden in threads)
This is the function:
Code: Pascal  [Select][+][-]
  1. Procedure Outputtext(TextToShow:String;showhour:boolean=true);
  2. Begin
  3. if showhour then texttoshow := timetostr(now)+' '+TextToShow;
  4. MainMemoStrings.Add(TextToShow);
  5. End;  

Uhm... why do you say that Outputtext does not modify visual components, but then post code that cleary shows that it does?  %)

If you don't want to use TThread class instead of BeginThread() so that you can utilize TThread.Synchronize() which allows you to execute code inside the main thread you should maybe take a look at Application.QueueAsyncCall().

torbente

  • Sr. Member
  • ****
  • Posts: 325
    • Noso Main Page
Re: Threads and Kernel.dll
« Reply #2 on: July 18, 2018, 12:25:05 am »
Why do you think it is a good idea to cast a function with the signature "function: String" to a procedure variable of type TThreadFunc that expects the signature "function(Pointer): PtrInt"? Change your thread function to the correct signature and remove the cast. This might have been one source of problems.  ;D
Im not sure what are you  refering with "Change your thread function to the correct signature and remove the cast". What i just realized (thanks to your answer) is that RunMiner can be a procedure, since it is not expected to return a value.
Now im testing with:
Code: Pascal  [Select][+][-]
  1. Procedure RunMiner();
 

Uhm... why do you say that Outputtext does not modify visual components, but then post code that cleary shows that it does?  %)

If you don't want to use TThread class instead of BeginThread() so that you can utilize TThread.Synchronize() which allows you to execute code inside the main thread you should maybe take a look at Application.QueueAsyncCall().

Code: Pascal  [Select][+][-]
  1. MainMemoStrings: TStringList;

The name is confusing (sorry for that), but MainMemoStrings is a simple TStringlist.
Noso Cryptocurrency Main Developer
https://github.com/DevTeamNoso/NosoWallet

taazz

  • Hero Member
  • *****
  • Posts: 5368
Re: Threads and Kernel.dll
« Reply #3 on: July 18, 2018, 02:42:19 pm »
Why do you think it is a good idea to cast a function with the signature "function: String" to a procedure variable of type TThreadFunc that expects the signature "function(Pointer): PtrInt"? Change your thread function to the correct signature and remove the cast. This might have been one source of problems.  ;D
Im not sure what are you  refering with "Change your thread function to the correct signature and remove the cast".
if you have to type cast to be accepted, then you have the wrong signature, that is not allowed in function/procedure callbacks. For example the declaration of the beginthread function is
Code: Pascal  [Select][+][-]
  1. type
  2.   TThreadFunc = function(parameter : pointer) : ptrint;
  3.  
  4. function BeginThread(ThreadFunction : tthreadfunc) : TThreadID;
  5.  
ThreadFunction in this case is a call back function that is going to be called outside the fpc environment(rtl, compiler etc) from the OS. When you write
Code: Pascal  [Select][+][-]
  1.     if ((OptionsData.Mining) and (not MINER_IsMinerOn) and (STATUS_Updated) and (not MINER_BlockFound) and (MINER_TargetHash = copy(LOCAL_LastBlockHash,1,MINER_TargetChars))) then
  2.     begin
  3.        OutputText('Mining. Target: '+MINER_TargetHash);
  4.        MINER_IsMinerOn := true;
  5.        Beginthread(tthreadfunc(@RunMiner)); //this one is wrong
  6.     end
You shoot your self in the foot and after settling down from jumping around in pain you shoot your self in the foot again to make sure its your foot. You can not pass RunMiner to the beginthread unless you change its signature to
Code: Pascal  [Select][+][-]
  1. function RunMiner(aParam:Pointer):PtrInt;//<--- this is the signature of a function/procedure its declaration, it must much the ThreadFunc type exactly in types not paramater names.
  2. var
  3.   HashValue : String = '';
  4. begin
  5.   MINER_ResultHash := '';
  6.   if MINER_HashCounter = 2147483647 then
  7.   begin
  8.    MINER_HashCounter := 0;
  9.    MINER_HashSeed := GetHashSeed();
  10.   end;
  11.   while ((MINER_HashCounter < 2147483647) and (OptionsData.Mining) and (STATUS_Updated) and (MINER_IsMinerOn)) do
  12.    begin
  13.     HashValue := HashMD5String(MINER_HashSeed+IntToStr(MINER_HashCounter));
  14.     if AnsiContainsStr(HashValue,MINER_TargetHash) then
  15.      begin
  16.       MINER_ResultHash := MINER_HashSeed+IntToStr(MINER_HashCounter);
  17.       OutputText('Hash Found for '+MINER_TargetHash+' : '+MINER_ResultHash);
  18.       MINER_HashSeed := GetHashSeed();
  19.       MINER_HashCounter := 0;
  20.       MINER_BlockFound := true;
  21.       break;
  22.      end;
  23.     MINER_HashCounter := MINER_HashCounter+1;
  24.    end;
  25.   MINER_IsMinerOn := false;
  26.   OutputText('Miner is OFF');
  27. end;
  28.  
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

torbente

  • Sr. Member
  • ****
  • Posts: 325
    • Noso Main Page
Re: Threads and Kernel.dll
« Reply #4 on: July 21, 2018, 07:35:17 am »
Quote
You shoot your self in the foot and after settling down from jumping around in pain you shoot your self in the foot again to make sure its your foot. You can not pass RunMiner to the beginthread unless you change its signature to

I changed the signature and readed some documentation about it. Now, the app is hanging sometimes (not much really) and i did not reveive more kernel errors.
Maybe i have an infinite loop fired when the run miner function finds a result under a rare cirscunstances; it will explain why now it only hangs very few times. (i will sanitize the code this weekend)

I modify the function a bit (removed the break and stringlists calls), so it is now:

Code: Pascal  [Select][+][-]
  1. function RunMiner(aParam:Pointer):PtrInt;
  2. var
  3.   HashValue : String = '';
  4. begin
  5. MINER_ResultHash := '';
  6. MINER_HashCounter := 0;
  7. MINER_HashSeed := GetHashSeed();
  8. while ((MINER_HashCounter < 99999999) and (OptionsData.Mining) and (STATUS_Updated)
  9.   and (MINER_IsMinerOn) and (not MINER_BlockFound)) do
  10.    begin
  11.    HashValue := HashMD5String(MINER_HashSeed+IntToStr(MINER_HashCounter));
  12.    if AnsiContainsStr(HashValue,MINER_TargetHash) then
  13.       begin
  14.       MINER_ResultHash := MINER_HashSeed+IntToStr(MINER_HashCounter);
  15.       MINER_HashSeed := GetHashSeed();
  16.       MINER_HashCounter := 0;
  17.       MINER_BlockFound := true;
  18.       end;
  19.    MINER_HashCounter := MINER_HashCounter+1;
  20.    end;
  21. MINER_IsMinerOn := false;
  22. end;

Do i need to manage (free, destroy?) the result of the function when it finishes? AFAIK, it is freed when the thread finishes. (corect me if im wrong)

Thanks a lot.
Noso Cryptocurrency Main Developer
https://github.com/DevTeamNoso/NosoWallet

torbente

  • Sr. Member
  • ****
  • Posts: 325
    • Noso Main Page
Re: Threads and Kernel.dll
« Reply #5 on: July 28, 2018, 09:15:27 pm »
If i add:

Code: Pascal  [Select][+][-]
  1. MyThreaddFunction : Integer;
  2. ...
  3. MyThreaddFunction:=Beginthread(tthreadfunc(@RunMiner));

Can i check if the thread is still active? so i could call...

Code: Pascal  [Select][+][-]
  1. killthread(MyThreaddFunction);
  2. MyThreaddFunction:=Beginthread(tthreadfunc(@RunMiner));

... to avoid having 2 threads doing the same thing?
Noso Cryptocurrency Main Developer
https://github.com/DevTeamNoso/NosoWallet

taazz

  • Hero Member
  • *****
  • Posts: 5368
Re: Threads and Kernel.dll
« Reply #6 on: July 29, 2018, 12:00:49 am »
If i add:

Code: Pascal  [Select][+][-]
  1. MyThreaddFunction : Integer;
  2. ...
  3. MyThreaddFunction:=Beginthread(tthreadfunc(@RunMiner));

Can i check if the thread is still active? so i could call...

Code: Pascal  [Select][+][-]
  1. killthread(MyThreaddFunction);
  2. MyThreaddFunction:=Beginthread(tthreadfunc(@RunMiner));

... to avoid having 2 threads doing the same thing?
Well there are is a problem with this approach, race condition. Its conceivable that between checking if the thread is alive and killing the thread, it finishes and exits, in which case you are back in your starting place.
It is far better to kill the thread and check the results or catch any exception that is raised, and then replace the id with the new one.

There is option of course to zero out the variable MyThreaddFunction from inside RunMiner as the last line of code but this will also lead to a race condition what happens if after you call kill the thread leaves for long enough to allow you to replace the value with the new one and then exits zeroing out the new id instead of the old?

Well you get the point.
« Last Edit: July 29, 2018, 12:03:56 am by taazz »
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

torbente

  • Sr. Member
  • ****
  • Posts: 325
    • Noso Main Page
Re: Threads and Kernel.dll
« Reply #7 on: July 29, 2018, 11:57:17 pm »
Well you get the point.

I understand what you minds,. So lets me explain what i can do to avoid a race condition:

Code: Pascal  [Select][+][-]
  1. OldThread, NewThread: integer;
  2. ...
  3. NewThread:=Beginthread(tthreadfunc(@RunMiner));

Then, to start a new one;
Code: Pascal  [Select][+][-]
  1. OldThread := NewThread;
  2. NewThread:=Beginthread(tthreadfunc(@RunMiner));
  3. If {OldThread is alive} then KillThread(OldThread);

There is no problem if the thread work is duplicate some milliseconds, but i want avoid many threads opening that will top the CPU to 100%.

I believe it should work. But, how i can check if OldThread is alive? Also, are there any way to know how many threads the app is running? Maybe creating an array and adding a new record before each time i call Beginthread? Then, i could check the array and if it have more than 1 record, just delete all but the last one.






Noso Cryptocurrency Main Developer
https://github.com/DevTeamNoso/NosoWallet

taazz

  • Hero Member
  • *****
  • Posts: 5368
Re: Threads and Kernel.dll
« Reply #8 on: July 30, 2018, 12:31:21 am »
Well you get the point.

I understand what you minds,. So lets me explain what i can do to avoid a race condition:

Code: Pascal  [Select][+][-]
  1. OldThread, NewThread: integer;
  2. ...
  3. NewThread:=Beginthread(tthreadfunc(@RunMiner));

Then, to start a new one;
Code: Pascal  [Select][+][-]
  1. OldThread := NewThread;
  2. NewThread:=Beginthread(tthreadfunc(@RunMiner));
  3. If {OldThread is alive} then KillThread(OldThread);
waste of cpu cycles, you missed the point. you code something along the lines of
Code: Pascal  [Select][+][-]
  1. try
  2.   KillThread(ThreadID);
  3. except
  4.   on e:Excpetion do begin
  5.     //do nothing
  6.   end;
  7. end;
  8. ThreadID:=Beginthread(tthreadfunc(@RunMiner));
  9.  
There is no problem if the thread work is duplicate some milliseconds, but i want avoid many threads opening that will top the CPU to 100%.

I believe it should work. But, how i can check if OldThread is alive? Also, are there any way to know how many threads the app is running? Maybe creating an array and adding a new record before each time i call Beginthread? Then, i could check the array and if it have more than 1 record, just delete all but the last one.
Well since you seem stuck in the moment, here is how I would check if the thread is active on windows.
Code: Pascal  [Select][+][-]
  1. uses ......, windows;
  2. {$IFDEF WINDOWS}
  3. function IsThreadActive(const aThread:THandle):Boolean;
  4. var
  5.   vRes,vExitCode:DWORD;
  6. begin
  7.   vRes := GetExitCodeThread(aThread, vExitCode);
  8.   if vRes = 0 then RaiseLastOSError;
  9.   Result := vExitCode = STILL_ACTIVE;
  10. end;
  11. {$ENDIF}
  12.  
Be warned this has been typed from memory directly in the browser and it sould be considered as pseudo code not working sample if it compiles don't forget to test it before you put in use.
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

 

TinyPortal © 2005-2018