cthreads.pp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697
  1. {
  2. $Id$
  3. This file is part of the Free Pascal run time library.
  4. Copyright (c) 2002 by Peter Vreman,
  5. member of the Free Pascal development team.
  6. Linux (pthreads) threading support implementation
  7. See the file COPYING.FPC, included in this distribution,
  8. for details about the copyright.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  12. **********************************************************************}
  13. {$mode objfpc}
  14. {$ifdef linux}
  15. {$define dynpthreads} // Useless on BSD, since they are in libc
  16. {$endif}
  17. unit cthreads;
  18. interface
  19. {$S-}
  20. {$ifndef dynpthreads} // If you have problems compiling this on FreeBSD 5.x
  21. {$linklib c} // try adding -Xf
  22. {$ifndef Darwin}
  23. {$linklib pthread}
  24. {$endif darwin}
  25. {$endif}
  26. Procedure SetCThreadManager;
  27. implementation
  28. Uses
  29. BaseUnix,
  30. unix,
  31. unixtype,
  32. sysutils
  33. {$ifdef dynpthreads}
  34. ,dl
  35. {$endif}
  36. ;
  37. {*****************************************************************************
  38. Generic overloaded
  39. *****************************************************************************}
  40. { Include OS specific parts. }
  41. {$i pthread.inc}
  42. Type PINTRTLEvent = ^TINTRTLEvent;
  43. TINTRTLEvent = record
  44. condvar: pthread_cond_t;
  45. mutex: pthread_mutex_t;
  46. end;
  47. {*****************************************************************************
  48. Threadvar support
  49. *****************************************************************************}
  50. {$ifdef HASTHREADVAR}
  51. const
  52. threadvarblocksize : dword = 0;
  53. var
  54. TLSKey : pthread_key_t;
  55. procedure CInitThreadvar(var offset : dword;size : dword);
  56. begin
  57. {$ifdef cpusparc}
  58. threadvarblocksize:=align(threadvarblocksize,16);
  59. {$endif cpusparc}
  60. {$ifdef cpupowerpc}
  61. threadvarblocksize:=align(threadvarblocksize,8);
  62. {$endif cpupowerc}
  63. {$ifdef cpui386}
  64. threadvarblocksize:=align(threadvarblocksize,8);
  65. {$endif cpui386}
  66. {$ifdef cpuarm}
  67. threadvarblocksize:=align(threadvarblocksize,4);
  68. {$endif cpuarm}
  69. {$ifdef cpum68k}
  70. threadvarblocksize:=align(threadvarblocksize,2);
  71. {$endif cpum68k}
  72. {$ifdef cpux86_64}
  73. threadvarblocksize:=align(threadvarblocksize,16);
  74. {$endif cpux86_64}
  75. offset:=threadvarblocksize;
  76. inc(threadvarblocksize,size);
  77. end;
  78. function CRelocateThreadvar(offset : dword) : pointer;
  79. begin
  80. CRelocateThreadvar:=pthread_getspecific(tlskey)+Offset;
  81. end;
  82. procedure CAllocateThreadVars;
  83. var
  84. dataindex : pointer;
  85. begin
  86. { we've to allocate the memory from system }
  87. { because the FPC heap management uses }
  88. { exceptions which use threadvars but }
  89. { these aren't allocated yet ... }
  90. { allocate room on the heap for the thread vars }
  91. DataIndex:=Pointer(Fpmmap(nil,threadvarblocksize,3,MAP_PRIVATE+MAP_ANONYMOUS,-1,0));
  92. FillChar(DataIndex^,threadvarblocksize,0);
  93. pthread_setspecific(tlskey,dataindex);
  94. end;
  95. procedure CReleaseThreadVars;
  96. begin
  97. {$ifdef ver1_0}
  98. Fpmunmap(longint(pthread_getspecific(tlskey)),threadvarblocksize);
  99. {$else}
  100. Fpmunmap(pointer(pthread_getspecific(tlskey)),threadvarblocksize);
  101. {$endif}
  102. end;
  103. { Include OS independent Threadvar initialization }
  104. {$endif HASTHREADVAR}
  105. {*****************************************************************************
  106. Thread starting
  107. *****************************************************************************}
  108. type
  109. pthreadinfo = ^tthreadinfo;
  110. tthreadinfo = record
  111. f : tthreadfunc;
  112. p : pointer;
  113. stklen : cardinal;
  114. end;
  115. procedure DoneThread;
  116. begin
  117. { Release Threadvars }
  118. {$ifdef HASTHREADVAR}
  119. CReleaseThreadVars;
  120. {$endif HASTHREADVAR}
  121. end;
  122. function ThreadMain(param : pointer) : pointer;cdecl;
  123. var
  124. ti : tthreadinfo;
  125. {$ifdef DEBUG_MT}
  126. // in here, don't use write/writeln before having called
  127. // InitThread! I wonder if anyone ever debugged these routines,
  128. // because they will have crashed if DEBUG_MT was enabled!
  129. // this took me the good part of an hour to figure out
  130. // why it was crashing all the time!
  131. // this is kind of a workaround, we simply write(2) to fd 0
  132. s: string[100]; // not an ansistring
  133. {$endif DEBUG_MT}
  134. begin
  135. {$ifdef DEBUG_MT}
  136. s := 'New thread started, initing threadvars'#10;
  137. fpwrite(0,s[1],length(s));
  138. {$endif DEBUG_MT}
  139. {$ifdef HASTHREADVAR}
  140. { Allocate local thread vars, this must be the first thing,
  141. because the exception management and io depends on threadvars }
  142. CAllocateThreadVars;
  143. {$endif HASTHREADVAR}
  144. { Copy parameter to local data }
  145. {$ifdef DEBUG_MT}
  146. s := 'New thread started, initialising ...'#10;
  147. fpwrite(0,s[1],length(s));
  148. {$endif DEBUG_MT}
  149. ti:=pthreadinfo(param)^;
  150. dispose(pthreadinfo(param));
  151. { Initialize thread }
  152. InitThread(ti.stklen);
  153. { Start thread function }
  154. {$ifdef DEBUG_MT}
  155. writeln('Jumping to thread function');
  156. {$endif DEBUG_MT}
  157. ThreadMain:=pointer(ti.f(ti.p));
  158. DoneThread;
  159. pthread_detach(pthread_t(pthread_self()));
  160. end;
  161. function CBeginThread(sa : Pointer;stacksize : dword;
  162. ThreadFunction : tthreadfunc;p : pointer;
  163. creationFlags : dword; var ThreadId : THandle) : DWord;
  164. var
  165. ti : pthreadinfo;
  166. thread_attr : pthread_attr_t;
  167. begin
  168. {$ifdef DEBUG_MT}
  169. writeln('Creating new thread');
  170. {$endif DEBUG_MT}
  171. { Initialize multithreading if not done }
  172. if not IsMultiThread then
  173. begin
  174. {$ifdef HASTHREADVAR}
  175. { We're still running in single thread mode, setup the TLS }
  176. pthread_key_create(@TLSKey,nil);
  177. InitThreadVars(@CRelocateThreadvar);
  178. {$endif HASTHREADVAR}
  179. IsMultiThread:=true;
  180. end;
  181. { the only way to pass data to the newly created thread
  182. in a MT safe way, is to use the heap }
  183. new(ti);
  184. ti^.f:=ThreadFunction;
  185. ti^.p:=p;
  186. ti^.stklen:=stacksize;
  187. { call pthread_create }
  188. {$ifdef DEBUG_MT}
  189. writeln('Starting new thread');
  190. {$endif DEBUG_MT}
  191. pthread_attr_init(@thread_attr);
  192. pthread_attr_setinheritsched(@thread_attr, PTHREAD_EXPLICIT_SCHED);
  193. // will fail under linux -- apparently unimplemented
  194. pthread_attr_setscope(@thread_attr, PTHREAD_SCOPE_PROCESS);
  195. // don't create detached, we need to be able to join (waitfor) on
  196. // the newly created thread!
  197. //pthread_attr_setdetachstate(@thread_attr, PTHREAD_CREATE_DETACHED);
  198. if pthread_create(@threadid, @thread_attr, @ThreadMain,ti) <> 0 then begin
  199. threadid := 0;
  200. end;
  201. CBeginThread:=threadid;
  202. {$ifdef DEBUG_MT}
  203. writeln('BeginThread returning ',CBeginThread);
  204. {$endif DEBUG_MT}
  205. end;
  206. procedure CEndThread(ExitCode : DWord);
  207. begin
  208. DoneThread;
  209. pthread_detach(pthread_t(pthread_self()));
  210. pthread_exit(pointer(ptrint(ExitCode)));
  211. end;
  212. function CSuspendThread (threadHandle : TThreadID) : dword;
  213. begin
  214. {$Warning SuspendThread needs to be implemented}
  215. end;
  216. function CResumeThread (threadHandle : TThreadID) : dword;
  217. begin
  218. {$Warning ResumeThread needs to be implemented}
  219. end;
  220. procedure CThreadSwitch; {give time to other threads}
  221. begin
  222. {extern int pthread_yield (void) __THROW;}
  223. {$Warning ThreadSwitch needs to be implemented}
  224. end;
  225. function CKillThread (threadHandle : TThreadID) : dword;
  226. begin
  227. pthread_detach(pthread_t(threadHandle));
  228. CKillThread := pthread_cancel(pthread_t(threadHandle));
  229. end;
  230. function CWaitForThreadTerminate (threadHandle : TThreadID; TimeoutMs : longint) : dword; {0=no timeout}
  231. var
  232. LResultP: Pointer;
  233. LResult: DWord;
  234. begin
  235. LResult := 0;
  236. LResultP := @LResult;
  237. pthread_join(pthread_t(threadHandle), @LResultP);
  238. CWaitForThreadTerminate := LResult;
  239. end;
  240. {$warning threadhandle can be larger than a dword}
  241. function CThreadSetPriority (threadHandle : TThreadID; Prio: longint): boolean; {-15..+15, 0=normal}
  242. begin
  243. {$Warning ThreadSetPriority needs to be implemented}
  244. end;
  245. {$warning threadhandle can be larger than a dword}
  246. function CThreadGetPriority (threadHandle : TThreadID): Integer;
  247. begin
  248. {$Warning ThreadGetPriority needs to be implemented}
  249. end;
  250. function CGetCurrentThreadId : TThreadID;
  251. begin
  252. CGetCurrentThreadId:=dword(pthread_self());
  253. end;
  254. {*****************************************************************************
  255. Delphi/Win32 compatibility
  256. *****************************************************************************}
  257. procedure CInitCriticalSection(var CS);
  258. var
  259. MAttr : pthread_mutexattr_t;
  260. res: longint;
  261. begin
  262. res:=pthread_mutexattr_init(@MAttr);
  263. if res=0 then
  264. begin
  265. res:=pthread_mutexattr_settype(@MAttr,longint(_PTHREAD_MUTEX_RECURSIVE));
  266. if res=0 then
  267. res := pthread_mutex_init(@CS,@MAttr)
  268. else
  269. { No recursive mutex support :/ }
  270. res := pthread_mutex_init(@CS,NIL);
  271. end
  272. else
  273. res:= pthread_mutex_init(@CS,NIL);
  274. pthread_mutexattr_destroy(@MAttr);
  275. if res <> 0 then
  276. runerror(6);
  277. end;
  278. procedure CEnterCriticalSection(var CS);
  279. begin
  280. if pthread_mutex_lock(@CS) <> 0 then
  281. runerror(6);
  282. end;
  283. procedure CLeaveCriticalSection(var CS);
  284. begin
  285. if pthread_mutex_unlock(@CS) <> 0 then
  286. runerror(6)
  287. end;
  288. procedure CDoneCriticalSection(var CS);
  289. begin
  290. if pthread_mutex_destroy(@CS) <> 0 then
  291. runerror(6);
  292. end;
  293. {*****************************************************************************
  294. Heap Mutex Protection
  295. *****************************************************************************}
  296. var
  297. HeapMutex : pthread_mutex_t;
  298. procedure PThreadHeapMutexInit;
  299. begin
  300. pthread_mutex_init(@heapmutex,nil);
  301. end;
  302. procedure PThreadHeapMutexDone;
  303. begin
  304. pthread_mutex_destroy(@heapmutex);
  305. end;
  306. procedure PThreadHeapMutexLock;
  307. begin
  308. pthread_mutex_lock(@heapmutex);
  309. end;
  310. procedure PThreadHeapMutexUnlock;
  311. begin
  312. pthread_mutex_unlock(@heapmutex);
  313. end;
  314. const
  315. PThreadMemoryMutexManager : TMemoryMutexManager = (
  316. MutexInit : @PThreadHeapMutexInit;
  317. MutexDone : @PThreadHeapMutexDone;
  318. MutexLock : @PThreadHeapMutexLock;
  319. MutexUnlock : @PThreadHeapMutexUnlock;
  320. );
  321. procedure InitHeapMutexes;
  322. begin
  323. SetMemoryMutexManager(PThreadMemoryMutexManager);
  324. end;
  325. type
  326. TPthreadMutex = pthread_mutex_t;
  327. Tbasiceventstate=record
  328. FSem: Pointer;
  329. FManualReset: Boolean;
  330. FEventSection: TPthreadMutex;
  331. end;
  332. plocaleventstate = ^tbasiceventstate;
  333. // peventstate=pointer;
  334. Const
  335. wrSignaled = 0;
  336. wrTimeout = 1;
  337. wrAbandoned= 2;
  338. wrError = 3;
  339. function IntBasicEventCreate(EventAttributes : Pointer; AManualReset,InitialState : Boolean;const Name : ansistring):pEventState;
  340. var
  341. MAttr : pthread_mutexattr_t;
  342. res : cint;
  343. begin
  344. new(plocaleventstate(result));
  345. plocaleventstate(result)^.FManualReset:=AManualReset;
  346. plocaleventstate(result)^.FSem:=New(PSemaphore); //sem_t.
  347. // plocaleventstate(result)^.feventsection:=nil;
  348. res:=pthread_mutexattr_init(@MAttr);
  349. if res=0 then
  350. begin
  351. res:=pthread_mutexattr_settype(@MAttr,longint(_PTHREAD_MUTEX_RECURSIVE));
  352. if Res=0 then
  353. Res:=pthread_mutex_init(@plocaleventstate(result)^.feventsection,@MAttr)
  354. else
  355. res:=pthread_mutex_init(@plocaleventstate(result)^.feventsection,nil);
  356. end
  357. else
  358. res:=pthread_mutex_init(@plocaleventstate(result)^.feventsection,nil);
  359. pthread_mutexattr_destroy(@MAttr);
  360. if res <> 0 then
  361. runerror(6);
  362. if sem_init(psem_t(plocaleventstate(result)^.FSem),ord(False),Ord(InitialState)) <> 0 then
  363. runerror(6);
  364. end;
  365. procedure Intbasiceventdestroy(state:peventstate);
  366. begin
  367. sem_destroy(psem_t( plocaleventstate(state)^.FSem));
  368. end;
  369. procedure IntbasiceventResetEvent(state:peventstate);
  370. begin
  371. While sem_trywait(psem_t( plocaleventstate(state)^.FSem))=0 do
  372. ;
  373. end;
  374. procedure IntbasiceventSetEvent(state:peventstate);
  375. Var
  376. Value : Longint;
  377. begin
  378. pthread_mutex_lock(@plocaleventstate(state)^.feventsection);
  379. Try
  380. sem_getvalue(plocaleventstate(state)^.FSem,@value);
  381. if Value=0 then
  382. sem_post(psem_t( plocaleventstate(state)^.FSem));
  383. finally
  384. pthread_mutex_unlock(@plocaleventstate(state)^.feventsection);
  385. end;
  386. end;
  387. function IntbasiceventWaitFor(Timeout : Cardinal;state:peventstate) : longint;
  388. begin
  389. If TimeOut<>Cardinal($FFFFFFFF) then
  390. result:=wrError
  391. else
  392. begin
  393. sem_wait(psem_t(plocaleventstate(state)^.FSem));
  394. result:=wrSignaled;
  395. if plocaleventstate(state)^.FManualReset then
  396. begin
  397. pthread_mutex_lock(@plocaleventstate(state)^.feventsection);
  398. Try
  399. intbasiceventresetevent(State);
  400. sem_post(psem_t( plocaleventstate(state)^.FSem));
  401. Finally
  402. pthread_mutex_unlock(@plocaleventstate(state)^.feventsection);
  403. end;
  404. end;
  405. end;
  406. end;
  407. function intRTLEventCreate: PRTLEvent;
  408. var p:pintrtlevent;
  409. begin
  410. new(p);
  411. pthread_cond_init(@p^.condvar, nil);
  412. pthread_mutex_init(@p^.mutex, nil);
  413. result:=PRTLEVENT(p);
  414. end;
  415. procedure intRTLEventDestroy(AEvent: PRTLEvent);
  416. var p:pintrtlevent;
  417. begin
  418. p:=pintrtlevent(aevent);
  419. pthread_cond_destroy(@p^.condvar);
  420. pthread_mutex_destroy(@p^.mutex);
  421. dispose(p);
  422. end;
  423. procedure intRTLEventSetEvent(AEvent: PRTLEvent);
  424. var p:pintrtlevent;
  425. begin
  426. p:=pintrtlevent(aevent);
  427. pthread_mutex_lock(@p^.mutex);
  428. pthread_cond_signal(@p^.condvar);
  429. pthread_mutex_unlock(@p^.mutex);
  430. end;
  431. procedure intRTLEventResetEvent(AEvent: PRTLEvent);
  432. begin
  433. { events before startwait are ignored unix }
  434. end;
  435. procedure intRTLEventStartWait(AEvent: PRTLEvent);
  436. var p:pintrtlevent;
  437. begin
  438. p:=pintrtlevent(aevent);
  439. pthread_mutex_lock(@p^.mutex);
  440. end;
  441. procedure intRTLEventWaitFor(AEvent: PRTLEvent);
  442. var p:pintrtlevent;
  443. begin
  444. p:=pintrtlevent(aevent);
  445. pthread_cond_wait(@p^.condvar, @p^.mutex);
  446. pthread_mutex_unlock(@p^.mutex);
  447. end;
  448. procedure intRTLEventWaitForTimeout(AEvent: PRTLEvent;timeout : longint);
  449. var
  450. p : pintrtlevent;
  451. errres : cint;
  452. timespec : ttimespec;
  453. begin
  454. p:=pintrtlevent(aevent);
  455. timespec.tv_sec:=timeout div 1000;
  456. timespec.tv_nsec:=(timeout mod 1000)*1000000;
  457. errres:=pthread_cond_timedwait(@p^.condvar, @p^.mutex, @timespec);
  458. if (errres=0) or (errres=ESysETIMEDOUT) then
  459. pthread_mutex_unlock(@p^.mutex);
  460. end;
  461. type
  462. threadmethod = procedure of object;
  463. Function CInitThreads : Boolean;
  464. begin
  465. {$ifdef DEBUG_MT}
  466. Writeln('Entering InitThreads.');
  467. {$endif}
  468. {$ifndef dynpthreads}
  469. Result:=True;
  470. {$else}
  471. Result:=LoadPthreads;
  472. {$endif}
  473. ThreadID := SizeUInt (pthread_self);
  474. {$ifdef DEBUG_MT}
  475. Writeln('InitThreads : ',Result);
  476. {$endif DEBUG_MT}
  477. end;
  478. Function CDoneThreads : Boolean;
  479. begin
  480. {$ifndef dynpthreads}
  481. Result:=True;
  482. {$else}
  483. Result:=UnloadPthreads;
  484. {$endif}
  485. end;
  486. Var
  487. CThreadManager : TThreadManager;
  488. Procedure SetCThreadManager;
  489. begin
  490. With CThreadManager do
  491. begin
  492. InitManager :=@CInitThreads;
  493. DoneManager :=@CDoneThreads;
  494. BeginThread :=@CBeginThread;
  495. EndThread :=@CEndThread;
  496. SuspendThread :=@CSuspendThread;
  497. ResumeThread :=@CResumeThread;
  498. KillThread :=@CKillThread;
  499. ThreadSwitch :=@CThreadSwitch;
  500. WaitForThreadTerminate :=@CWaitForThreadTerminate;
  501. ThreadSetPriority :=@CThreadSetPriority;
  502. ThreadGetPriority :=@CThreadGetPriority;
  503. GetCurrentThreadId :=@CGetCurrentThreadId;
  504. InitCriticalSection :=@CInitCriticalSection;
  505. DoneCriticalSection :=@CDoneCriticalSection;
  506. EnterCriticalSection :=@CEnterCriticalSection;
  507. LeaveCriticalSection :=@CLeaveCriticalSection;
  508. {$ifdef hasthreadvar}
  509. InitThreadVar :=@CInitThreadVar;
  510. RelocateThreadVar :=@CRelocateThreadVar;
  511. AllocateThreadVars :=@CAllocateThreadVars;
  512. ReleaseThreadVars :=@CReleaseThreadVars;
  513. {$endif}
  514. BasicEventCreate :=@intBasicEventCreate;
  515. BasicEventDestroy :=@intBasicEventDestroy;
  516. BasicEventResetEvent :=@intBasicEventResetEvent;
  517. BasicEventSetEvent :=@intBasicEventSetEvent;
  518. BasiceventWaitFor :=@intBasiceventWaitFor;
  519. rtlEventCreate :=@intrtlEventCreate;
  520. rtlEventDestroy :=@intrtlEventDestroy;
  521. rtlEventSetEvent :=@intrtlEventSetEvent;
  522. rtlEventResetEvent :=@intrtlEventResetEvent;
  523. rtlEventStartWait :=@intrtlEventStartWait;
  524. rtleventWaitForTimeout :=@intrtleventWaitForTimeout;
  525. rtleventWaitFor :=@intrtleventWaitFor;
  526. end;
  527. SetThreadManager(CThreadManager);
  528. InitHeapMutexes;
  529. end;
  530. initialization
  531. if ThreadingAlreadyUsed then
  532. begin
  533. writeln('Threading has been used before cthreads was initialized.');
  534. writeln('Make cthreads one of the first units in your uses clause.');
  535. runerror(211);
  536. end;
  537. SetCThreadManager;
  538. finalization
  539. end.
  540. {
  541. $Log$
  542. Revision 1.28 2005-04-13 20:10:50 florian
  543. + TThreadID
  544. Revision 1.27 2005/04/09 18:45:43 florian
  545. * fixed some unix stuff
  546. Revision 1.26 2005/04/09 17:26:08 florian
  547. + classes.mainthreadid is set now
  548. + rtleventresetevent
  549. + rtleventwairfor with timeout
  550. + checksynchronize with timeout
  551. * race condition in synchronize fixed
  552. Revision 1.25 2005/04/03 19:29:28 florian
  553. * proper error message if the cthreads unit is included too late
  554. uses clause
  555. Revision 1.24 2005/02/25 22:10:27 florian
  556. * final fix for linux (hopefully)
  557. Revision 1.23 2005/02/25 22:02:48 florian
  558. * another "transfer to linux"-commit
  559. Revision 1.22 2005/02/25 21:52:07 florian
  560. * "transfer to linux"-commit
  561. Revision 1.21 2005/02/14 17:13:31 peter
  562. * truncate log
  563. Revision 1.20 2005/02/06 11:20:52 peter
  564. * threading in system unit
  565. * removed systhrds unit
  566. }