| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403 | {    This file is part of the Free Pascal run time library.    Copyright (c) 2015 by Karoly Balogh,    member of the Free Pascal development team.    native threadmanager implementation for Amiga-like systems    See the file COPYING.FPC, included in this distribution,    for details about the copyright.    This program is distributed in the hope that it will be useful,    but WITHOUT ANY WARRANTY; without even the implied warranty of    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. **********************************************************************}{$mode objfpc}{$IFNDEF FPC_DOTTEDUNITS}unit athreads;{$ENDIF FPC_DOTTEDUNITS}interfaceprocedure SetAThreadBaseName(s: String);function GetAThreadProcess(threadID: TThreadID): Pointer;implementation{ enable this to compile athreads easily outside the RTL }{.$DEFINE ATHREADS_STANDALONE}{$IFDEF ATHREADS_STANDALONE}uses  exec, amigados, utility;{$ELSE}{ * Include required system specific includes * }{$include execd.inc}{$include execf.inc}{$include timerd.inc}{$include doslibd.inc}{$include doslibf.inc}{$ifdef cpum68k}{$if defined(amiga_v1_0_only) or defined(amiga_v1_2_only) or defined(amiga_v2_0_only)}{$include legacyexech.inc}{$if not defined(amiga_v2_0_only)}{$include legacydosh.inc}{$endif}{$endif}{$endif}{$ENDIF}const  threadvarblocksize : dword = 0;var  SubThreadBaseName: String = 'FPC Subthread';{.$define DEBUG_MT}{.$define DEBUG_AMIEVENT}type  TThreadOperation = ( toNone, toStart, toResume, toExit );type  PThreadMsg = ^TThreadMsg;  PThreadInfo = ^TThreadInfo;  TThreadInfo = record    threadVars: Pointer;     { have threadvars ptr as first field, so no offset is needed to access it (faster) }    threadVarsSize: DWord;   { size of the allocated threadvars block }    nextThread: PThreadInfo; { threadinfos are a linked list, using this field }    threadPtr: PProcess;     { our thread pointer, as returned by CreateNewProc(). invalid after exited field is true! }    threadID: TThreadID;     { thread Unique ID }    stackLen: PtrUInt;       { stack size the thread was construced with }    exitCode: Pointer;       { exitcode after the process has exited     }    f: TThreadFunc;          { ThreadFunc function pointer }    p: Pointer;              { ThreadFunc argument }    flags: dword;            { Flags this thread were created with }    num: longint;            { This was the "num"th thread to created }    mainthread: boolean;     { true if this is our main thread }    exited: boolean;         { true if the thread has exited, and can be cleaned up }    startSuspended: boolean; { true if the thread was started suspended, and not resumed yet }    suspended: boolean;      { true if the thread is currently suspended }    mutex: TSignalSemaphore; { thread's mutex. locked during the thread's life. }    name: String;            { Thread's name }  end;  TThreadMsg = record    tm_MsgNode   : TMessage;    tm_ThreadInfo: PThreadInfo;    tm_Operation : TThreadOperation;  end;var  AThreadManager: TThreadManager;  AThreadMainThreadInfo: TThreadInfo;  AThreadList: PThreadInfo;  AThreadListLen: LongInt;  AThreadNum: LongInt;  AThreadListSemaphore: TSignalSemaphore;{ Simple IntToStr() replacement which works with ShortStrings }function IToStr(const i: LongInt): String;begin  Str(I,result);end;{$IFDEF DEBUG_MT}function IToHStr(const i: LongInt): String;begin  result:=HexStr(Pointer(i));end;{$ENDIF}{ Function to add a thread to the running threads list }procedure AddToThreadList(var l: PThreadInfo; ti: PThreadInfo);var  p     : PThreadInfo;  inList: Boolean;begin  inList:=False;  ObtainSemaphore(@AThreadListSemaphore);  if l = nil then    { if the list is not yet allocated, the newly added      threadinfo will be the first item }    l:=ti  else    begin      { otherwise, look for the last item and append }      p:=l;      while (p^.nextThread<>nil) do p:=p^.nextThread;      p^.nextThread:=ti;    end;  inc(AThreadNum);  ti^.num:=AThreadNum;  inc(AThreadListLen);{$IFDEF DEBUG_MT}  SysDebugLn('FPC AThreads: thread ID:'+IToHStr(ti^.threadID)+' added, now '+IToStr(AThreadListLen)+' thread(s) in list.');{$ENDIF}  ReleaseSemaphore(@AThreadListSemaphore);end;{ Function to remove a thread from running threads list }function RemoveFromThreadList(var l: PThreadInfo; threadID: TThreadID): boolean;var  p      : PThreadInfo;  pprev  : PThreadInfo;  inList : Boolean;  tmpNext: PThreadInfo;  tmpInfo: PThreadInfo;begin  inList:=False;  if l=nil then    begin      RemoveFromThreadList:=inList;      exit;    end;  ObtainSemaphore(@AThreadListSemaphore);  p:=l;  pprev:=nil;  while (p <> nil) and (p^.threadID <> threadID) do    begin      pprev:=p;      p:=p^.nextThread;    end;  if p <> nil then    begin      tmpNext:=p^.nextThread;      if not p^.mainthread and p^.exited then        begin{$IFDEF DEBUG_MT}          SysDebugLn('FPC AThreads: Releasing resources for thread ID:'+IToHStr(threadID));          if (p^.threadVars <> nil) or (p^.threadVarsSize <> 0) then            SysDebugLn('FPC AThreads: WARNING, threadvars area wasn''t properly freed!'+IToHStr(threadID));{$ENDIF}          dispose(p);          if pprev <> nil then            pprev^.nextThread:=tmpNext;          Dec(AThreadListLen);        end      else        begin{$IFDEF DEBUG_MT}          SysDebugLn('FPC AThreads: Error! Attempt to remove threadID, which is the mainthread or not exited:'+IToHStr(threadID));{$ENDIF}          inList:=false;        end;    end{$IFDEF DEBUG_MT}  else    SysDebugLn('FPC AThreads: Error! Attempt to remove threadID, which is not in list:'+IToHstr(threadID)){$ENDIF}  ;  ReleaseSemaphore(@AThreadListSemaphore);  RemoveFromThreadList:=inList;end;{ Function to return a function's ThreadInfo based on the threadID }function GetThreadInfo(var l: PThreadInfo; threadID: TThreadID): PThreadInfo;var  p     : PThreadInfo;  inList: Boolean;begin  inList:=False;  GetThreadInfo:=nil;  if l = nil then    exit;  ObtainSemaphoreShared(@AThreadListSemaphore);  p:=l;  while (p <> nil) and (p^.threadID <> threadID) do    p:=p^.nextThread;  GetThreadInfo:=p;  ReleaseSemaphore(@AThreadListSemaphore);end;{ Function to check if a threadInfo is a threadInfo from our list }function IsValidThreadInfo(var l: PThreadInfo; threadInfo: PThreadInfo): Boolean;var  p: PThreadInfo;begin  IsValidThreadInfo:=false;  if (l = nil) or (threadInfo = nil) then    exit;  ObtainSemaphoreShared(@AThreadListSemaphore);  p:=l;  while (p <> nil) and (p <> threadinfo) do    p:=p^.nextThread;  IsValidThreadInfo:=p<>nil;  ReleaseSemaphore(@AThreadListSemaphore);end;{ Get current thread ThreadInfo structure }function GetCurrentThreadInfo: PThreadInfo;begin  result:=PThreadInfo(PProcess(FindTask(nil))^.pr_Task.tc_UserData);end;{ Returns the number of threads still not exited in our threadlist }function CountRunningThreads(var l: PThreadInfo): LongInt;var  p: PThreadInfo;begin  CountRunningThreads:=0;  ObtainSemaphoreShared(@AThreadListSemaphore);  p:=l;  while p <> nil do    begin      inc(CountRunningThreads,ord(not p^.exited));      p:=p^.nextThread;    end;  ReleaseSemaphore(@AThreadListSemaphore);end;{ Helper function for IPC }procedure SendMessageToThread(var threadMsg: TThreadMsg; p: PThreadInfo; const op: TThreadOperation; waitReply: boolean);var  replyPort: PMsgPort;begin  replyPort:=@PProcess(FindTask(nil))^.pr_MsgPort;  FillChar(threadMsg,sizeof(threadMsg),0);  with threadMsg do    begin      with tm_MsgNode do        begin          mn_Node.ln_Type:=NT_MESSAGE;          mn_Length:=SizeOf(TThreadMsg);          if waitReply then            mn_ReplyPort:=replyPort          else            mn_ReplyPort:=nil;        end;      tm_ThreadInfo:=p;      tm_Operation:=op;    end;  PutMsg(@p^.threadPtr^.pr_MsgPort,@threadMsg);  if waitReply then    begin      WaitPort(replyPort);      GetMsg(replyPort);    end;end;procedure SetAThreadBaseName(s: String);begin  ObtainSemaphore(@AThreadListSemaphore);  SubThreadBaseName:=s;  ReleaseSemaphore(@AThreadListSemaphore);end;function GetAThreadBaseName: String;begin  ObtainSemaphoreShared(@AThreadListSemaphore);  GetAThreadBaseName:=SubThreadBaseName;  ReleaseSemaphore(@AThreadListSemaphore);end;function GetAThreadProcess(threadID: TThreadID): Pointer;begin  GetAThreadProcess:=nil;  ObtainSemaphoreShared(@AThreadListSemaphore);  with PThreadInfo(threadID)^ do    begin      if not exited then        GetAThreadProcess:=threadPtr;    end;  ReleaseSemaphore(@AThreadListSemaphore);end;procedure AInitThreadvar(var offset : dword;size : dword);begin{$IFDEF DEBUG_MT}  {SysDebugLn('FPC AThreads: InitThreadvar');}{$ENDIF}  offset:=threadvarblocksize;  inc(threadvarblocksize,size);end;function ARelocateThreadvar(offset : dword) : pointer;var  p: PThreadInfo;begin{$IFDEF DEBUG_MT}  {SysDebugLn('FPC AThreads: RelocateThreadvar');}{$ENDIF}  p:=GetCurrentThreadInfo;  if (p <> nil) and (p^.threadVars <> nil) then    result:=p^.threadVars + Offset  else    result:=nil;end;procedure AAllocateThreadVars;var  p: PThreadInfo;begin  { we've to allocate the memory from system  }  { because the FPC heap management uses      }  { exceptions which use threadvars but       }  { these aren't allocated yet ...            }  { allocate room on the heap for the thread vars }  p:=GetCurrentThreadInfo;  if p <> nil then    begin{$ifdef DEBUG_MT}      SysDebugLn('FPC AThreads: Allocating threadvars, ID:'+IToHStr(p^.threadID));{$endif}{$ifdef AMIGA}      ObtainSemaphore(ASYS_heapSemaphore);{$endif}      p^.threadVars:=AllocPooled(ASYS_heapPool,threadvarblocksize);      if p^.threadVars = nil then        SysDebugLn('FPC AThreads: Failed to allocate threadvar memory!')      else        begin          p^.threadVarsSize:=threadvarblocksize;          FillChar(p^.threadVars^,threadvarblocksize,0);        end;{$ifdef AMIGA}      ReleaseSemaphore(ASYS_heapSemaphore);{$endif}    end  else    begin{$ifdef DEBUG_MT}      SysDebugLn('FPC AThreads: AllocateThreadVars: tc_UserData of this process was nil!'){$endif}    end;end;procedure AReleaseThreadVars;var  p: PThreadInfo;begin  p:=GetCurrentThreadInfo;  if (p <> nil) and (p^.threadVars <> nil) then    begin{$ifdef DEBUG_MT}      SysDebugLn('FPC AThreads: Releasing threadvars, ID:'+IToHStr(p^.threadID));{$endif}{$ifdef AMIGA}      ObtainSemaphore(ASYS_heapSemaphore);{$endif}      FreePooled(ASYS_heapPool,p^.threadVars,p^.threadVarsSize);      p^.threadVars:=nil;      p^.threadVarsSize:=0;{$ifdef AMIGA}      ReleaseSemaphore(ASYS_heapSemaphore);{$endif}    end  else    begin{$ifdef DEBUG_MT}      SysDebugLn('FPC AThreads: ReleaseThreadVars: tc_UserData or threadVars area of this process was nil!'){$endif}    end;end;procedure InitAThreading;var  threadInfo: PThreadInfo;  p: PProcess;begin  if (InterLockedExchange(longint(IsMultiThread),ord(true)) = 0) then    begin      { We're still running in single thread mode, setup the TLS }{$ifdef DEBUG_MT}      SysDebugLn('FPC AThreads: Entering multithreaded mode...');{$endif}      p:=PProcess(FindTask(nil));      { the main thread info is allocated as a global var, it is the cleanest solution,        as it can never really be freed after threading was initialized, due to clashes        with threadvar handling in heap managers, etc. }      threadInfo:=@AThreadMainThreadInfo;      FillChar(threadInfo^,sizeof(TThreadInfo),0);      p^.pr_Task.tc_UserData:=threadInfo;      threadInfo^.mainThread:=true;      InitSemaphore(@threadInfo^.mutex);      ObtainSemaphore(@threadInfo^.mutex);      threadInfo^.threadPtr:=p;      threadInfo^.threadID:=TThreadID(threadInfo);      InitThreadVars(@ARelocateThreadvar);      AddToThreadList(AThreadList,threadInfo);    end;end;procedure ThreadFunc; cdecl;var  thisThread: PProcess;  threadMsg: PThreadMsg;  resumeMsg: PThreadMsg;  exitSuspend: boolean; // true if we have to exit instead of resuming  threadInfo: PThreadInfo;begin  thisThread:=PProcess(FindTask(nil));  { wait for our start message to arrive, then fetch it }  WaitPort(@thisThread^.pr_MsgPort);  threadMsg:=PThreadMsg(GetMsg(@thisThread^.pr_MsgPort));  { fetch existing threadinfo from the start message, and set    it to tc_userData, so we can proceed with threadvars }  threadInfo:=threadMsg^.tm_ThreadInfo;  thisThread^.pr_Task.tc_userData:=threadInfo;{$ifdef DEBUG_MT}  SysDebugLn('FPC AThreads: Entering subthread function, ID:'+hexStr(threadInfo));{$endif}  { Obtain the threads' mutex, used for exit sync }  ObtainSemaphore(@threadInfo^.mutex);  { Allocate local thread vars, this must be the first thing,    because the exception management and io depends on threadvars }  AAllocateThreadVars;  { Rename the thread into something sensible }  if threadInfo^.name <> '' then    begin{$ifdef DEBUG_MT}      { this line can't be before threadvar allocation }      SysDebugLn('FPC AThreads: Renaming thread ID:'+hexStr(threadInfo)+' to '+threadInfo^.name);{$endif}      thisThread^.pr_Task.tc_Node.ln_Name:=PAnsiChar(@threadInfo^.name[1]);    end;  { Reply the message, so the calling thread could continue }  { note that threadMsg was allocated on the caller's task, so }  { it will be invalid below this point }  ReplyMsg(PMessage(threadMsg));  { if creating a suspended thread, wait for the wakeup message to arrive }  { then check if we actually have to resume, or exit }  exitSuspend:=false;  if threadInfo^.startSuspended then    begin{$ifdef DEBUG_MT}      SysDebugLn('FPC AThreads: Suspending subthread on entry, ID:'+hexStr(threadInfo));{$endif}      WaitPort(@thisThread^.pr_MsgPort);      resumeMsg:=PThreadMsg(GetMsg(@thisThread^.pr_MsgPort));      exitSuspend:=resumeMsg^.tm_Operation <> toResume;      threadInfo^.startSuspended:=false;      ReplyMsg(PMessage(resumeMsg));{$ifdef DEBUG_MT}      SysDebugLn('FPC AThreads: Resuming subthread on entry, ID:'+hexStr(threadInfo)+', resumed only to exit: '+IToStr(ord(exitSuspend)));{$endif}    end;  { Finally, call the user code }  if not exitSuspend then    begin      InitThread(threadInfo^.stackLen);      DoThreadInitProcChain;      threadInfo^.exitCode:=Pointer(threadInfo^.f(threadInfo^.p));      DoThreadExitProcChain;      DoneThread;    end;{$ifdef DEBUG_MT}  SysDebugLn('FPC AThreads: Exiting Subthread function, ID:'+hexStr(threadInfo));{$endif}  Forbid();  threadInfo^.exited:=true;  threadInfo^.threadPtr:=nil;  { Finally, Release our exit mutex. }  ReleaseSemaphore(@threadInfo^.mutex);end;function CreateNewProcess(const Tags : Array Of PtrUInt) : PProcess;begin  result:=CreateNewProc(@Tags[0]);end;function ABeginThread(sa : Pointer;stacksize : PtrUInt;                      ThreadFunction : tthreadfunc;p : pointer;                      creationFlags : dword; var ThreadId : TThreadId) : TThreadID;var  threadInfo: PThreadInfo;  threadMsg: TThreadMsg;  threadName: String;  subThread: PProcess;begin  ABeginThread:=TThreadID(0);{$ifdef DEBUG_MT}  SysDebugLn('FPC AThreads: Creating new thread...');{$endif DEBUG_MT}  { Initialize multithreading if not done }  if not IsMultiThread then    InitAThreading;  { the only way to pass data to the newly created thread    in a MT safe way, is to use the heap }  new(threadInfo);  FillChar(threadInfo^,sizeof(TThreadInfo),0);  InitSemaphore(@threadInfo^.mutex);  threadInfo^.f:=ThreadFunction;  threadInfo^.p:=p;  if (creationFlags and STACK_SIZE_PARAM_IS_A_RESERVATION) > 0 then    threadInfo^.stackLen:=stacksize  else    threadInfo^.stackLen:=System.StackLength; { inherit parent's stack size }  threadInfo^.startSuspended:=(creationFlags and CREATE_SUSPENDED) > 0;{$ifdef DEBUG_MT}  SysDebugLn('FPC AThreads: Starting new thread... Stack size: '+IToStr(threadInfo^.stackLen));{$endif}  subThread:=CreateNewProcess([NP_Entry,PtrUInt(@ThreadFunc),                               {$IFDEF MORPHOS}                               NP_CodeType,CODETYPE_PPC,                               NP_PPCStackSize,threadInfo^.stacklen,                               {$ELSE}                               NP_StackSize,threadInfo^.stacklen,                               {$ENDIF}                               TAG_DONE]);  if subThread = nil then    begin{$ifdef DEBUG_MT}      SysDebugLn('FPC AThreads: Failed to start the subthread!');{$endif}      exit;    end;  ThreadID:=TThreadID(threadInfo);  threadInfo^.threadPtr:=subThread;  threadInfo^.threadID:=ThreadID;  AddToThreadList(AThreadList,threadInfo);  { the thread should be started, and waiting for our start message, so send it }{$ifdef DEBUG_MT}  SysDebugLn('FPC AThreads: Sending start message to subthread and waiting for reply, ID:'+IToHStr(threadID));{$endif}  { AddToThreadList assigned us a number, so use it to name the thread }  threadInfo^.name:=GetAThreadBaseName+' #'+IToStr(threadInfo^.num);  SendMessageToThread(threadMsg,threadInfo,toStart,true);  ABeginThread:=ThreadId;{$ifdef DEBUG_MT}  SysDebugLn('FPC AThreads: Thread created successfully, ID:'+IToHStr(threadID));{$endif}end;procedure AEndThread(ExitCode : DWord);begin  { Do not call DoneThread here. It will be called by the threadfunction, when it exits. }end;function ASuspendThread (threadHandle : TThreadID) : dword;var  p: PThreadInfo;  m: PThreadMsg;begin  ASuspendThread:=0;  if GetCurrentThreadID = threadHandle then    begin      p:=GetThreadInfo(AThreadList,threadHandle);      if p <> nil then        begin          p^.suspended:=true;          while p^.suspended do            begin              WaitPort(@p^.threadPtr^.pr_MsgPort);              m:=PThreadMsg(GetMsg(@p^.threadPtr^.pr_MsgPort));              if m^.tm_Operation = toResume then                p^.suspended:=false              else{$ifdef DEBUG_MT}                SysDebugLn('FPC AThreads: Got message during suspend, but it wasn''t toResume! ID:'+IToHStr(threadHandle)){$endif}              ;              ReplyMsg(PMessage(m));            end;        end;    end  else    begin{$ifdef DEBUG_MT}      SysDebugLn('FPC AThreads: SuspendThread called for ID:'+IToHStr(threadHandle)+' which is not the current thread!');{$endif}      result:=dword(-1);    end;end;function AResumeThread (threadHandle : TThreadID) : dword;var  m: TThreadMsg;  p: PThreadInfo;begin  AResumeThread:=0;  Forbid();  p:=GetThreadInfo(AThreadList,threadHandle);  if (p <> nil) and (p^.suspended or p^.startSuspended) then    begin{$ifdef DEBUG_MT}      SysDebugLn('FPC AThreads: Waiting for thread to resume, ID:'+IToHStr(threadHandle));{$endif}      { WaitPort in SendMessageToThread will break the Forbid() state... }      SendMessageToThread(m,p,toResume,true);      AResumeThread:=0;    end  else    begin{$ifdef DEBUG_MT}      SysDebugLn('FPC AThreads: Error, attempt to resume a non-suspended thread, or invalid thread ID:'+IToHStr(threadHandle));{$endif}      AResumeThread:=dword(-1);    end;  Permit();end;procedure AThreadSwitch;  {give time to other threads}begin  { On Unix, this calls sched_yield();    Harry 'Piru' Sintonen recommended to emulate this on Amiga systems with    exec/Forbid-exec/Permit pair which is pretty fast to execute and will    trigger a rescheduling.    Another idea by Frank Mariak was to use exec/SetTaskPri() with the same    priority }  Forbid();  Permit();end;function AKillThread (threadHandle : TThreadID) : dword;begin{$ifdef DEBUG_MT}  SysDebugLn('FPC AThreads: unsupported operation: KillThread called for ID:'+IToHStr(threadHandle));{$endif}  // cannot be properly supported on Amiga  AKillThread:=dword(-1);end;function ACloseThread (threadHandle : TThreadID) : dword;begin{$WARNING The return value here seems to be undocumented}  RemoveFromThreadList(AThreadList, threadHandle);  result:=0;end;function AWaitForThreadTerminate (threadHandle : TThreadID; TimeoutMs : longint) : dword;  {0=no timeout}var  p: PThreadInfo;  m: TThreadMsg;begin{.$WARNING Support for timeout argument is not implemented}{ But since CThreads uses pthread_join, which has also no timeout,  I don't think this is a big issue. (KB) }  AWaitForThreadTerminate:=0;  Forbid();  p:=GetThreadInfo(AThreadList,threadHandle);  if (p <> nil) then    begin      if not p^.exited then        begin{$ifdef DEBUG_MT}          SysDebugLn('FPC AThreads: Waiting for thread to exit, ID:'+IToHStr(threadHandle));{$endif}          { WaitPort in SendMessageToThread will break the Forbid() state... }          if p^.startSuspended then            begin              SendMessageToThread(m,p,toExit,true);{$ifdef DEBUG_MT}              SysDebugLn('FPC AThreads: Signaled start-suspended thread to exit, ID:'+IToHStr(threadHandle));{$endif}            end;          { Wait for the thread to exit... }          Permit();          ObtainSemaphore(@p^.mutex);          ReleaseSemaphore(@p^.mutex);          Forbid();        end      else{$ifdef DEBUG_MT}        SysDebugLn('FPC AThreads: Thread already exited, ID:'+IToHStr(threadHandle));{$endif}      AWaitForThreadTerminate:=DWord(p^.exitCode);    end  else    begin{$ifdef DEBUG_MT}      SysDebugLn('FPC AThreads: Error, attempt to wait for invalid thread ID to exit, ID:'+IToHStr(threadHandle));{$endif}      AWaitForThreadTerminate:=dword(-1); { Return non-zero code on error. }    end;  Permit();end;function AThreadSetPriority (threadHandle : TThreadID; Prio: longint): boolean; {-15..+15, 0=normal}begin  {$Warning ThreadSetPriority needs to be implemented}  result:=false;end;function AThreadGetPriority (threadHandle : TThreadID): Integer;begin  {$Warning ThreadGetPriority needs to be implemented}  result:=0;end;function AGetCurrentThreadId : TThreadID;begin  AGetCurrentThreadId := TThreadID(GetCurrentThreadInfo);end;procedure ASetThreadDebugNameA(threadHandle: TThreadID; const ThreadName: AnsiString);var  p: PThreadInfo;  MyProcess: PProcess;begin{$ifdef DEBUG_MT}  SysDebugLn('FPC AThreads: Set threadname to ' + ThreadName + ' for Thread ' + IToStr(threadHandle));{$endif}  Forbid();  if threadHandle = -1 then  begin    MyProcess := PProcess(FindTask(nil));    P := PThreadInfo(MyProcess^.pr_Task.tc_userData);    if not IsValidThreadInfo(AThreadList,p) then      P := nil;  end  else  begin    p:=GetThreadInfo(AThreadList, threadHandle);    if p <> nil then      MyProcess := p^.threadPtr;  end;  if (p <> nil) and (MyProcess <> nil) and (ThreadName <> '') then  begin    p^.name := ThreadName;    MyProcess^.pr_Task.tc_Node.ln_Name := PAnsiChar(@p^.name[1]);  end;  Permit();end;procedure ASetThreadDebugNameU(threadHandle: TThreadID; const ThreadName: UnicodeString);begin  ASetThreadDebugNameA(threadHandle, AnsiString(ThreadName));end;Type  PINTRTLEvent = ^TINTRTLEvent;      TINTRTLEvent = record        isset: boolean;        Sem: TSignalSemaphore; // Semaphore to protect the whole stuff      end;Function intRTLEventCreate: PRTLEvent;var p:pintrtlevent;begin  new(p);  p^.isset:=false;  InitSemaphore(@p^.Sem);  result:=PRTLEVENT(p);end;procedure intRTLEventDestroy(AEvent: PRTLEvent);var p:pintrtlevent;begin  p:=pintrtlevent(aevent);  dispose(p);end;procedure intRTLEventSetEvent(AEvent: PRTLEvent);var p:pintrtlevent;begin  p:=pintrtlevent(aevent);  ObtainSemaphore(@p^.Sem);  p^.isset:=true;  ReleaseSemaphore(@p^.Sem);end;procedure intRTLEventResetEvent(AEvent: PRTLEvent);var p:pintrtlevent;begin  p:=pintrtlevent(aevent);  ObtainSemaphore(@p^.Sem);  p^.isset:=false;  ReleaseSemaphore(@p^.Sem);end;procedure intRTLEventWaitFor(AEvent: PRTLEvent);var p:pintrtlevent;begin  p:=pintrtlevent(aevent);  ObtainSemaphore(@p^.Sem);  while not p^.isset do    begin      ReleaseSemaphore(@p^.Sem);      DOSDelay(1);      ObtainSemaphore(@p^.Sem);    end;  p^.isset:=false;  ReleaseSemaphore(@p^.Sem);end;procedure intRTLEventWaitForTimeout(AEvent: PRTLEvent;timeout : longint);var  p : pintrtlevent;begin  p:=pintrtlevent(aevent);  timeout:=timeout div 20; // DOSDelay expects (1/50 seconds)  ObtainSemaphore(@p^.Sem);  while (not p^.isset) and (timeout > 0) do    begin      ReleaseSemaphore(@p^.Sem);      DOSDelay(1);      dec(timeout);      ObtainSemaphore(@p^.Sem);    end;  p^.isset:=false;  ReleaseSemaphore(@p^.Sem);end;procedure AInitCriticalSection(var CS);begin{$IFDEF DEBUG_MT}  SysDebugLn('FPC AThreads: InitCriticalSection '+hexStr(@CS));{$ENDIF}  InitSemaphore(PSignalSemaphore(@CS));end;procedure AEnterCriticalSection(var CS);begin{$IFDEF DEBUG_MT}  SysDebugLn('FPC AThreads: EnterCriticalSection '+hexStr(@CS));{$ENDIF}  ObtainSemaphore(PSignalSemaphore(@CS));end;function ATryEnterCriticalSection(var CS):longint;begin{$IFDEF DEBUG_MT}  SysDebugLn('FPC AThreads: TryEnterCriticalSection '+hexStr(@CS));{$ENDIF}  result:=DWord(AttemptSemaphore(PSignalSemaphore(@CS)));  if result<>0 then    result:=1;end;procedure ALeaveCriticalSection(var CS);begin{$IFDEF DEBUG_MT}  SysDebugLn('FPC AThreads: LeaveCriticalSection '+hexStr(@CS));{$ENDIF}  ReleaseSemaphore(PSignalSemaphore(@CS));end;procedure ADoneCriticalSection(var CS);begin{$IFDEF DEBUG_MT}  SysDebugLn('FPC AThreads: DoneCriticalSection '+hexStr(@CS));{$ENDIF}  { unlock as long as unlocking works to unlock it if it is recursive    some Delphi code might call this function with a locked mutex }  with TSignalSemaphore(CS) do    while ss_NestCount > 0 do      ReleaseSemaphore(PSignalSemaphore(@CS));end;// Event Stuff// Return values for WaitForconst  wrSignaled  = 0;  wrTimeout   = 1;  wrAbandoned = 2;  wrError     = 3;// the internal AmigaEventtype  TAmiEvent = record    IsSet: Boolean;  // the actual Event setting    Manual: Boolean; // do not automatically reset the event    Name: string; // Name for the event structure (needed for cross process)    Waiter: Integer; // number of WaitFor waiting for this event    Destroyed: Boolean; // the event is going to be destroyed, all WaitFor please leave first    Sem: TSignalSemaphore; // Semaphore to protect the whole stuff  end;  PAmiEvent = ^TAmiEvent;// Create an Eventfunction intBasicEventCreate(EventAttributes : Pointer;AManualReset,InitialState : Boolean;const Name : ansistring):pEventState;var  AmiEvent: PAmiEvent;begin  New(AmiEvent);  AmiEvent^.isSet := InitialState;  AmiEvent^.Name := Name;  AmiEvent^.Waiter := 0;  AmiEvent^.Manual := AManualReset;  AmiEvent^.Destroyed := False;  InitSemaphore(@AmiEvent^.Sem);  Result := AmiEvent;end;procedure intbasiceventdestroy(state:peventstate);var  AmiEvent: PAmiEvent absolute State;  Waiter: Integer;begin  if Assigned(AmiEvent) then  begin    ObtainSemaphore(@AmiEvent^.Sem);    AmiEvent^.Destroyed := True; // we destroy the event    ReleaseSemaphore(@AmiEvent^.Sem);    repeat      DosDelay(1);    until AmiEvent^.Waiter <= 0;    ObtainSemaphore(@AmiEvent^.Sem); // is there anyone still waiting for it?    ReleaseSemaphore(@AmiEvent^.Sem);    Dispose(AmiEvent);  end;end;procedure intbasiceventResetEvent(state:peventstate);var  AmiEvent: PAmiEvent absolute State;begin  if Assigned(AmiEvent) then  begin    {$IFDEF DEBUG_AMIEVENT}    SysDebugLn('AmiEvent: Reset Event');    {$ENDIF}    ObtainSemaphore(@AmiEvent^.Sem);    AmiEvent^.IsSet := False;    ReleaseSemaphore(@AmiEvent^.Sem);  end;end;procedure intbasiceventSetEvent(state:peventstate);var  AmiEvent: PAmiEvent absolute State;begin  if Assigned(AmiEvent) then  begin    {$IFDEF DEBUG_AMIEVENT}    SysDebugLn('AmiEvent: Set Event');    {$ENDIF}    ObtainSemaphore(@AmiEvent^.Sem);    AmiEvent^.IsSet := True;    ReleaseSemaphore(@AmiEvent^.Sem);  end;end;// Timer stuffprocedure NewList(List: PList); inline;begin  with List^ do  begin    lh_Head := PNode(@lh_Tail);    lh_Tail := nil;    lh_TailPred := PNode(@lh_Head)  end;end;function CreatePort(Name: PAnsiChar; Pri: LongInt): PMsgPort;var  SigBit: ShortInt;  Port: PMsgPort;begin  Sigbit := AllocSignal(-1);  if sigbit = -1 then    CreatePort := nil;  Port := ExecAllocMem(SizeOf(TMsgPort), MEMF_CLEAR);  if Port = nil then  begin    FreeSignal(SigBit);    CreatePort := nil;  end;  with port^ do  begin    if Assigned(Name) then      mp_Node.ln_Name := Name    else      mp_Node.ln_Name := nil;    mp_Node.ln_Pri := pri;    mp_Node.ln_Type := 4;    mp_Flags := 0;    mp_SigBit := SigBit;    mp_SigTask := FindTask(nil);  end;  if Assigned(Name) then    AddPort(Port)  else    NewList(Addr(Port^.mp_MsgList));  CreatePort := Port;end;procedure DeletePort(Port: PMsgPort);begin  if port <> nil then  begin    if Port^.mp_Node.ln_Name <> nil then      RemPort(Port);    Port^.mp_Node.ln_Type := $FF;    Port^.mp_MsgList.lh_Head := PNode(-1);    FreeSignal(Port^.mp_SigBit);    ExecFreeMem(Port, SizeOf(TMsgPort));  end;end;function CreateExtIO(Port: PMsgPort; Size: LongInt): PIORequest;begin  Result := nil;  if Port <> nil then  begin    Result := ExecAllocMem(Size, MEMF_CLEAR);    if Result <> nil then    begin      Result^.io_Message.mn_Node.ln_Type := 7;      Result^.io_Message.mn_Length := Size;      Result^.io_Message.mn_ReplyPort := Port;    end;  end;end;procedure DeleteExtIO (IoReq: PIORequest);begin  if IoReq <> nil then  begin    IoReq^.io_Message.mn_Node.ln_Type := $FF;    IoReq^.io_Message.mn_ReplyPort := PMsgPort(-1);    IoReq^.io_Device := PDevice(-1);    ExecFreeMem(IoReq, IoReq^.io_Message.mn_Length);  endend;function Create_Timer(TheUnit: LongInt): PTimeRequest;var  TimerPort: PMsgPort;begin  Result := nil;  TimerPort := CreatePort(nil, 0);  if TimerPort = nil then    Exit;  Result := PTimeRequest(CreateExtIO(TimerPort, SizeOf(TTimeRequest)));  if Result = Nil then  begin    DeletePort(TimerPort);    Exit;  end;  if OpenDevice(TIMERNAME, TheUnit, PIORequest(Result), 0) <> 0 then  begin    DeleteExtIO(PIORequest(Result));    DeletePort(TimerPort);    Result := nil;  end;end;Procedure Delete_Timer(WhichTimer: PTimeRequest);var  WhichPort: PMsgPort;begin  WhichPort := WhichTimer^.tr_Node.io_Message.mn_ReplyPort;  if assigned(WhichTimer) then  begin    CloseDevice(PIORequest(WhichTimer));    DeleteExtIO(PIORequest(WhichTimer));  end;  if Assigned(WhichPort) then    DeletePort(WhichPort);end;function GetEventTime(TR: PTimeRequest): Int64;begin  Result := -1;  if tr = nil then    Exit;  tr^.tr_node.io_Command := TR_GETSYSTIME;  DoIO(PIORequest(tr));  // structure assignment  Result := Int64(tr^.tr_time.TV_Secs) * 1000 + tr^.tr_time.TV_Micro div 1000;end;// End timer stuff// the mighty Waitfor routinefunction intbasiceventWaitFor(Timeout : Cardinal;state:peventstate;FUseComWait : Boolean=False) : longint;var  AmiEvent: PAmiEvent absolute State;  Tr: PTimeRequest = nil;  StartTime, CurTime: Int64;begin  {$IFDEF DEBUG_AMIEVENT}  SysDebugLn('AmiEvent: Enter WaitFor');  {$ENDIF}  Result := wrError;  if Assigned(AmiEvent) then  begin    // we do an initial Check    ObtainSemaphore(@AmiEvent^.Sem);    if AmiEvent^.Destroyed then    begin      Result := wrAbandoned; // we got destroyed, so we just leave      {$IFDEF DEBUG_AMIEVENT}      SysDebugLn('AmiEvent: WaitFor Early Destroy');      {$ENDIF}      ReleaseSemaphore(@AmiEvent^.Sem);      Exit;    end;    if AmiEvent^.IsSet then    begin      Result := wrSignaled; // signal Already set      if not AmiEvent^.Manual then        AmiEvent^.IsSet := False;      {$IFDEF DEBUG_AMIEVENT}      SysDebugLn('AmiEvent: WaitFor Early Signaled');      {$ENDIF}      ReleaseSemaphore(@AmiEvent^.Sem);      Exit;    end;    // signal not set, so we add this call to the waiterlist    Inc(AmiEvent^.Waiter);    ReleaseSemaphore(@AmiEvent^.Sem);    // that means we have to wait and care about the timeout -> need a timer    Tr := create_timer(UNIT_MICROHZ);    if not Assigned(Tr) then // cannot create timer :-O      Exit;    // time we started the actual waiting    StartTime := GetEventTime(TR);    try      // the main loop, notice the breaks are inside the Obtain/Release      // therefore the finally block must release it, (and no other exit allowed!)      repeat        CurTime := GetEventTime(TR); // to check the timeout, outside obtain/release to save some time        ObtainSemaphore(@AmiEvent^.Sem);        // check the status of event        if AmiEvent^.Destroyed then        begin          Result := wrAbandoned; // we got destroyed          {$IFDEF DEBUG_AMIEVENT}          SysDebugLn('AmiEvent: WaitFor Destroy');          {$ENDIF}          break;        end;        if AmiEvent^.IsSet then        begin          Result := wrSignaled; // signal got set          {$IFDEF DEBUG_AMIEVENT}          SysDebugLn('AmiEvent: WaitFor Signaled');          {$ENDIF}          Break;        end;        if CurTime - StartTime > Timeout then        begin          Result := wrTimeOut; // we got a timeout          {$IFDEF DEBUG_AMIEVENT}          SysDebugLn('AmiEvent: WaitFor TimeOut');          {$ENDIF}          Break;        end;        // if we reach here, nothing happend...        // we release the semaphore and wait for other threads to do something        ReleaseSemaphore(@AmiEvent^.Sem);        DosDelay(1);      until False;    finally      // reset the Event if needed      if (Result = wrSignaled) and (not AmiEvent^.Manual) then        AmiEvent^.IsSet := False;      // we finished so get us away from waiter list      Dec(AmiEvent^.Waiter);      ReleaseSemaphore(@AmiEvent^.Sem); // unlock the event      Delete_timer(tr); // timer not needed anymore    end;  end;  {$IFDEF DEBUG_AMIEVENT}  SysDebugLn('AmiEvent: Leave WaitFor');  {$ENDIF}end;// end Event stufffunction AInitThreads : Boolean;begin{$ifdef DEBUG_MT}  SysDebugLn('FPC AThreads: Entering InitThreads...');{$endif}  result:=true;  // We assume that if you set the thread manager, the application is multithreading.  InitAThreading;  ThreadID := TThreadID(GetCurrentThreadInfo);end;function ADoneThreads : Boolean;begin  result:=true;end;procedure SetAThreadManager;begin  with AThreadManager do begin    InitManager            :=@AInitThreads;    DoneManager            :=@ADoneThreads;    BeginThread            :=@ABeginThread;    EndThread              :=@AEndThread;    SuspendThread          :=@ASuspendThread;    ResumeThread           :=@AResumeThread;    KillThread             :=@AKillThread;    ThreadSwitch           :=@AThreadSwitch;    CloseThread            :=@ACloseThread;    WaitForThreadTerminate :=@AWaitForThreadTerminate;    ThreadSetPriority      :=@AThreadSetPriority;    ThreadGetPriority      :=@AThreadGetPriority;    GetCurrentThreadId     :=@AGetCurrentThreadId;    SetThreadDebugNameA    :=@ASetThreadDebugNameA;    SetThreadDebugNameU    :=@ASetThreadDebugNameU;    InitCriticalSection    :=@AInitCriticalSection;    DoneCriticalSection    :=@ADoneCriticalSection;    EnterCriticalSection   :=@AEnterCriticalSection;    TryEnterCriticalSection:=@ATryEnterCriticalSection;    LeaveCriticalSection   :=@ALeaveCriticalSection;    InitThreadVar          :=@AInitThreadVar;    RelocateThreadVar      :=@ARelocateThreadVar;    AllocateThreadVars     :=@AAllocateThreadVars;    ReleaseThreadVars      :=@AReleaseThreadVars;    BasicEventCreate       :=@intBasicEventCreate;    BasicEventDestroy      :=@intBasicEventDestroy;    BasicEventResetEvent   :=@intBasicEventResetEvent;    BasicEventSetEvent     :=@intBasicEventSetEvent;    BasiceventWaitFor      :=@intBasicEventWaitFor;    rtlEventCreate         :=@intrtlEventCreate;    rtlEventDestroy        :=@intrtlEventDestroy;    rtlEventSetEvent       :=@intrtlEventSetEvent;    rtlEventResetEvent     :=@intrtlEventResetEvent;    rtleventWaitForTimeout :=@intrtleventWaitForTimeout;    rtleventWaitFor        :=@intrtleventWaitFor;  end;  SetThreadManager(AThreadManager);end;Procedure InitSystemThreads; external name '_FPC_InitSystemThreads';{ This should only be called from the finalization }procedure WaitForAllThreads;var  p: PThreadInfo;  pn: PThreadInfo;begin  { If we are the main thread exiting, we have to wait for our subprocesses to    exit. Because AmigaOS won't clean up for us. Also, after exiting the main    thread the OS unloads all the code segments with code potentially still    running in the background... So even waiting here forever is better than    exiting with active threads, which will most likely just kill the OS    immediately. (KB) }  ObtainSemaphore(@AThreadListSemaphore);{$IFDEF DEBUG_MT}  if AThreadListLen > 1 then    begin      SysDebugLn('FPC AThreads: We have registered subthreads, checking their status...');      if CountRunningThreads(AThreadList) > 1 then        SysDebugLn('FPC AThreads: We have running subthreads, waiting for them to exit...');    end;{$ENDIF}  while CountRunningThreads(AThreadList) > 1 do    begin      ReleaseSemaphore(@AThreadListSemaphore);      DOSDelay(1);      { Reobtain the semaphore... }      ObtainSemaphore(@AThreadListSemaphore);    end;  if AThreadListLen > 1 then    begin{$IFDEF DEBUG_MT}      SysDebugLn('FPC AThreads: All threads exited but some lacking cleanup - trying to free up resources...');{$ENDIF}      p:=AThreadList;      while p <> nil do        begin          pn:=p^.nextThread;          if not p^.mainThread then            RemoveFromThreadList(AThreadList,p^.threadID);          p:=pn;        end;    end  else    begin{$IFDEF DEBUG_MT}      SysDebugLn('FPC AThreads: All threads exited normally.');{$ENDIF}    end;  ReleaseSemaphore(@AThreadListSemaphore);end;initialization  initsystemthreads;{$IFDEF DEBUG_MT}  SysDebugLn('FPC AThreads: Unit Initialization');{$ENDIF}  if ThreadingAlreadyUsed then    begin      writeln('Threading has been used before athreads was initialized.');      writeln('Make athreads one of the first units in your uses clause!');      runerror(211);    end;  AThreadList:=nil;  AThreadListLen:=0;  AThreadNum:=-1; { Mainthread will be 0. }  InitSemaphore(@AThreadListSemaphore);  SetAThreadManager;{$IFDEF DEBUG_MT}  SysDebugLn('FPC AThreads: Unit Initialization Done');{$ENDIF}finalization{$IFDEF DEBUG_MT}  SysDebugLn('FPC AThreads: Unit Finalization');{$ENDIF}  WaitForAllThreads;end.
 |