ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/public/ibx/trunk/runtime/sv5ipc.inc
Revision: 17
Committed: Sat Dec 28 19:22:24 2013 UTC (6 years, 6 months ago) by tony
File size: 20311 byte(s)
Log Message:
Committing updates for Release R1-0-5
Line File contents
1 {Used by ISQLMonitor and implements System V IPC}
2
3 const
4 IPCFileName: string = 'FB.SQL.MONITOR1_0';
5 cNumberOfSemaphores = 10;
6 cMutexSemaphore = 0;
7 cMonitorCounter = 1;
8 cReadReadyEventSemaphore = 2;
9 cReadFinishedEventSemaphore = 4;
10 cDataAvailableEventSemaphore = 6;
11 cWriterBusyEventSemaphore = 8;
12 cDefaultTimeout = 1; { 1 seconds }
13
14 {
15 The call to semctl in ipc is broken in FPC release 2.4.2 and earlier. Hence
16 need to replace with a working libc call. semtimedop is not present in 2.4.2 and earlier.
17 }
18
19 {$IF FPC_FULLVERSION <= 20402 }
20 Function real_semctl(semid:cint; semnum:cint; cmd:cint): cint; cdecl; varargs; external clib name 'semctl';
21
22 Function semctl(semid:cint; semnum:cint; cmd:cint; var arg: tsemun): cint;
23 begin
24 semctl := real_semctl(semid,semnum,cmd,pointer(arg));
25 end;
26 {$IFDEF HAS_SEMTIMEDOP}
27 Function semtimedop(semid:cint; sops: psembuf; nsops: cuint; timeOut: ptimespec): cint; cdecl; external clib name 'semtimedop';
28 {$ENDIF}
29 Function semget(key:Tkey; nsems:cint; semflg:cint): cint; cdecl; external clib name 'semget';
30 Function semop(semid:cint; sops: psembuf; nsops: cuint): cint; cdecl; external clib name 'semop';
31
32 function GetLastErrno: cint;
33 begin
34 Result := fpgetCerrno
35 end;
36 {$ELSE}
37 function GetLastErrno: cint;
38 begin
39 Result := fpgetErrno
40 end;
41
42 {$ENDIF}
43
44 type
45 TGlobalInterface = class;
46 {Interprocess Communication Objects. All platform dependent IPC is abstracted
47 into this set of objects }
48
49 { TIpcCommon }
50
51 TIpcCommon = class
52 private
53 function GetSa: PSecurityAttributes;
54 protected
55 FInitialiser: boolean; static;
56 FSemaphoreSetID: cint; static;
57 FSharedMemoryID: cint; static;
58 function sem_op(SemNum, op: integer; flags: cshort = 0): cint;
59 function sem_timedop(SemNum, op: integer; timeout_secs: integer; flags: cshort = 0): cint;
60 function GetSemValue(SemNum: integer): cint;
61 procedure SemInit(SemNum, AValue: cint);
62 public
63 property Sa : PSecurityAttributes read GetSa;
64 end;
65
66 { TSharedMemory }
67
68 {
69 The shared memory segment is used for interprocess communication and
70 holds both a message buffer and a number of shared variables. Shared
71 memory is allocated to each shared variable using the Allocate function.
72 An underlying assumption is that each process using the shared memory
73 calls "Allocate" in the same order and for the same memory sizes.
74
75 Linux:
76
77 The Linux implementation uses Linux shared memory. IPC_PRIVATE is used
78 to allocate the memory and the resulting memory id is written to a
79 well known file. By default this is in the current user's home directory,
80 but this can be over-ridden to specify a globally unique filename.
81
82 Access to the shared memory is restricted to the current user/group.
83 Note that the Linux semaphore set is also created with the shared memory.
84 }
85
86 TSharedMemory = class(TIpcCommon)
87 private
88 FBuffer: PChar;
89 FLastAllocationSize: integer;
90 FUnused: integer;
91 FBufptr: PChar;
92 procedure DropSharedMemory;
93 procedure GetSharedMemory(MemSize: integer);
94 public
95 constructor Create(MemSize: integer);
96 destructor Destroy; override;
97 function Allocate(Size: integer): PChar;
98 property LastAllocationSize: integer read FLastAllocationSize;
99 end;
100
101 {TMutex}
102
103 TMutex = class(TIpcCommon)
104 private
105 FMutexSemaphore: cint;
106 FLockCount: integer;
107 public
108 constructor Create(SemNumber: cint);
109 procedure Lock;
110 procedure Unlock;
111 end;
112
113 { TSingleLockGate }
114
115 {
116 A single lock gate is either open or closed. When open, any thread can pass
117 through it while, when closed, all threads are blocked as they try to pass
118 through the gate. When the gate is opened, all blocked threads are resumed.
119
120 There is an implementation assumption that only one writer thread at
121 a time (i.e. the thread which locks or unlocks the gate) can have access to
122 it at any one time. I.e. an external Mutex prevents race conditions.
123
124 Linux:
125
126 In the Linux implementation, the gate is implemented by a semaphore
127 and a share memory integer used as a bi-state variable. When the gate
128 is open, the bi-state variable is non-zero. It is set to zero when closed.
129 Another shared memory integer is used to count the number of waiting
130 threads, and a second semaphore is used to protect access to this.
131
132 The event semaphore is initialised to zero. When a thread passes through the gate
133 it checks the state. If open, the thread continues. If closed then it
134 increments the count of waiting threads and then decrements the semaphore
135 and hence enters an indefinite wait state.
136
137 When the gate is locked, the state is set to zero. When unlocked, the state
138 is set to one and the semaphore incremented by the number of waiting threads,
139 which itself is then zeroed.
140
141 Always initialised to the Unlocked state
142 }
143
144 TSingleLockGate = class(TIpcCommon)
145 private
146 FOwner: TGlobalInterface;
147 FSemaphore: cint;
148 FMutex: cint;
149 FSignalledState: PInteger;
150 FWaitingThreads: PInteger;
151 function GetWaitingThreads: integer;
152 public
153 constructor Create(SemNum: cint; AOwner: TGlobalInterface);
154 property WaitingThreads: integer read GetWaitingThreads;
155 public
156 procedure PassthroughGate;
157 procedure Unlock;
158 procedure Lock;
159 end;
160
161 { TMultilockGate }
162
163 { This type of Gate is used where several reader threads must pass
164 through the gate before it can be opened for a writer thread.
165
166 The reader threads register their interest by each locking the gate.
167 The writer thread then waits on the locked gate until all the reader
168 threads have separately unlocked the gate.
169
170 There is an underlying assumption of a single writer. A Mutex must
171 be used to control access to the gate from the writer side if this
172 assumption is invalid.
173
174 Linux:
175
176 The Linux implementation uses a single semaphore to implement the gate,
177 which is initialised to 1 (unlocked), and a count of the number of
178 threads that have locked the gate (LockCount). A mutex semaphore
179 protects access to the LockCount. When the gate is locked, the lockcount
180 is incremented and, if the LockCount was originally zero, the semaphore is
181 set to zero (Gate Closed).
182
183 Unlocking the gate, is the reverse. The LockCount is decremented and, if it
184 reaches zero, the semaphore is set to one (Gate Opened).
185
186 When a writer passes through the gate, it checks the LockCount, if zero it
187 proceeds to pass through the gate. Otherwise it decrements and waits on the
188 semaphore. When the writer resumes, it increments the semaphore in order
189 to return it to its unlocked state. The wait is a timed wait, as there is
190 a risk that a reader thread may terminate while the gate is locked. If the
191 LockCount is non-zero, it is decremented and the writer returns to wait on
192 the gate.
193
194 Always initialised to the Unlocked state
195 }
196
197 TMultilockGate = class(TIpcCommon)
198 private
199 FOnGateTimeout: TNotifyEvent;
200 FOwner: TGlobalInterface;
201 FSemaphore: cint;
202 FMutex: cint;
203 FLockCount: PInteger;
204 function GetLockCount: integer;
205 public
206 constructor Create(SemNum: cint; AOwner: TGlobalInterface);
207 procedure Lock;
208 procedure Unlock;
209 procedure PassthroughGate;
210 property LockCount: integer read GetLockCount;
211 property OnGateTimeout: TNotifyEvent read FOnGateTimeout write FOnGateTimeout;
212 end;
213
214 { TGlobalInterface }
215
216 TGlobalInterface = class(TIpcCommon)
217 private
218 FMaxBufferSize: integer;
219 FSharedMemory: TSharedMemory;
220 FWriteLock: TMutex;
221 FBuffer: PChar;
222 FTraceDataType,
223 FBufferSize: PInteger;
224 FTimeStamp: PDateTime;
225 FReadReadyEvent: TMultiLockGate;
226 FReadFinishedEvent: TMultiLockGate;
227 FDataAvailableEvent: TSingleLockGate;
228 FWriterBusyEvent: TSingleLockGate;
229 function GetMonitorCount: integer;
230 public
231 constructor Create;
232 destructor Destroy; override;
233 procedure IncMonitorCount;
234 procedure DecMonitorCount;
235 procedure SendTrace(TraceObject: TTraceObject);
236 procedure ReceiveTrace(TraceObject: TTraceObject);
237 property DataAvailableEvent: TSingleLockGate read FDataAvailableEvent;
238 property WriterBusyEvent: TSingleLockGate read FWriterBusyEvent;
239 property ReadReadyEvent: TMultiLockGate read FReadReadyEvent;
240 property ReadFinishedEvent: TMultiLockGate read FReadFinishedEvent;
241 property WriteLock: TMutex read FWriteLock;
242 property MonitorCount: integer read GetMonitorCount;
243 property SharedMemory: TSharedMemory read FSharedMemory;
244 property MaxBufferSize: integer read FMaxBufferSize;
245 end;
246
247 { TSharedMemory }
248
249 procedure TSharedMemory.GetSharedMemory(MemSize: integer);
250 var F: cint;
251 begin
252 {Get the Shared Memory and Semaphore IDs from the Global File if it exists
253 or create them and the file otherwise }
254
255 repeat
256 F := fpOpen(IPCFileName, O_WrOnly or O_Creat or O_Excl);
257 if F < 0 then
258 begin
259 if fpgetErrno = 17 {EEXIST} then
260 begin
261 { looks like it already exists}
262 Sleep(100);
263 F := fpOpen(IPCFileName,O_RdOnly);
264 if (F < 0) and (fpgetErrno = 2 {ENOENT}) then
265 {probably just got deleted }
266 else
267 if F < 0 then
268 IBError(ibxeCannotCreateSharedResource,['Error accessing IPC File - ' +
269 StrError(fpgetErrno)]);
270 end
271 else
272 IBError(ibxeCannotCreateSharedResource,['Error creating IPC File - ' +
273 StrError(fpgetErrno)]);
274 end
275 else
276 FInitialiser := true
277 until F >= 0;
278
279 if FInitialiser then
280 begin
281 FSharedMemoryID := shmget(IPC_PRIVATE,MemSize, IPC_CREAT or
282 S_IRUSR or S_IWUSR or S_IRGRP or S_IWGRP);
283 if FSharedMemoryID < 0 then
284 IBError(ibxeCannotCreateSharedResource,['Cannot create shared memory segment - ' +
285 StrError(fpgetErrno)]);
286
287 FSemaphoreSetID := semget(IPC_PRIVATE, cNumberOfSemaphores,IPC_CREAT or
288 S_IRUSR or S_IWUSR or S_IRGRP or S_IWGRP);
289 if FSemaphoreSetID < 0 then
290 IBError(ibxeCannotCreateSharedResource,['Cannot create shared semaphore set - ' +
291 StrError(fpgetErrno)]);
292
293 fpWrite(F,FSharedMemoryID,sizeof(FSharedMemoryID));
294 fpWrite(F,FSemaphoreSetID,sizeof(FSemaphoreSetID));
295 end
296 else
297 begin
298 fpRead(F,FSharedMemoryID,sizeof(FSharedMemoryID));
299 fpRead(F,FSemaphoreSetID,sizeof(FSemaphoreSetID));
300 if GetSemValue(cMonitorCounter) = 0 then
301 begin
302 FInitialiser := true;
303 //writeln('Opened file and is initialiser');
304 end
305 end;
306 fpClose(F);
307 end;
308
309 procedure TSharedMemory.DropSharedMemory;
310 var ds: TShmid_ds;
311 arg: tsemun;
312 begin
313 if shmctl(FSharedMemoryID,IPC_STAT,@ds) < 0 then
314 IBError(ibxeSV5APIError,['Error getting shared memory info' + strError(fpgetErrno)]);
315 if ds.shm_nattch = 0 then { we are the last one out - so, turn off the lights }
316 begin
317 shmctl(FSharedMemoryID,IPC_RMID,nil);
318 semctl(FSemaphoreSetID,0,IPC_RMID,arg);
319 DeleteFile(IPCFileName);
320 end;
321 end;
322
323 constructor TSharedMemory.Create(MemSize: integer);
324 begin
325 inherited Create;
326 FInitialiser := false;
327 GetSharedMemory(MemSize);
328 FBuffer := shmat(FSharedMemoryID,nil,0);
329 if PtrInt(FBuffer) = -1 then
330 IBError(ibxeCannotCreateSharedResource,[StrError(Errno)]);
331 FBufPtr := FBuffer;
332 FUnused := MemSize
333 end;
334
335 destructor TSharedMemory.Destroy;
336 begin
337 shmdt(FBuffer);
338 DropSharedMemory;
339 inherited Destroy;
340 end;
341
342 function TSharedMemory.Allocate(Size: integer): PChar;
343 begin
344 if Size > FUnused then
345 IBError(ibxeCannotCreateSharedResource, ['Not enough shared memory']);
346 Result := FBufPtr;
347
348 if Size = 0 then
349 begin
350 FLastAllocationSize := FUnused;
351 FUnused := 0
352 end
353 else
354 begin
355 FLastAllocationSize := Size;
356 Dec(FUnused,Size);
357 end;
358 Inc(FBufPtr,Size)
359 end;
360
361 { TIpcCommon }
362
363 function TIpcCommon.GetSa: PSecurityAttributes;
364 begin
365 Result := nil
366 end;
367
368 function TIpcCommon.sem_op(SemNum, op: integer; flags: cshort): cint;
369 var sembuf: TSEMbuf;
370 begin
371 sembuf.sem_num := SemNum;
372 sembuf.sem_op:= op;
373 sembuf.sem_flg := flags or SEM_UNDO;
374 Result := semop(FSemaphoreSetID,@sembuf,1);
375 end;
376
377 function TIpcCommon.sem_timedop(SemNum, op: integer; timeout_secs: integer;
378 flags: cshort): cint;
379 var sembuf: TSEMbuf;
380 timeout: TimeSpec;
381 begin
382 sembuf.sem_num := SemNum;
383 sembuf.sem_op:= op;
384 sembuf.sem_flg := flags or SEM_UNDO;
385 timeout.tv_sec := timeout_secs;
386 timeout.tv_nsec := 0;
387 {$IFDEF HAS_SEMTIMEDOP}
388 Result := semtimedop(FSemaphoreSetID,@sembuf,1,@timeout);
389 {$ELSE}
390 Result := semop(FSemaphoreSetID,@sembuf,1); {May hang on race condition}
391 {$ENDIF}
392 end;
393
394 function TIpcCommon.GetSemValue(SemNum: integer): cint;
395 var args :TSEMun;
396 begin
397 Result := semctl(FSemaphoreSetID,SemNum,SEM_GETVAL,args);
398 if Result < 0 then
399 IBError(ibxeSV5APIError,['GetSemValue: '+strError(GetLastErrno)]);
400 end;
401
402 procedure TIpcCommon.SemInit(SemNum, AValue: cint);
403 var args :TSEMun;
404 begin
405 //writeln('Initialising ',SemNum,' to ',AValue);
406 args.val := AValue;
407 if semctl(FSemaphoreSetID,SemNum,SEM_SETVAL,args) < 0 then
408 IBError(ibxeCannotCreateSharedResource,['Unable to initialise Semaphone ' +
409 IntToStr(SemNum) + '- ' + StrError(GetLastErrno)]);
410
411 end;
412
413 { TMutex }
414
415 constructor TMutex.Create(SemNumber: cint);
416 begin
417 inherited Create;
418 FMutexSemaphore := SemNumber;
419 if FInitialiser then
420 SemInit(FMutexSemaphore,1)
421 end;
422
423 { Obtain ownership of the Mutex and prevent other threads from accessing protected resource }
424
425 procedure TMutex.Lock;
426 begin
427 //writeln('Lock: Entering Mutex ',FMutexSemaphore,' LockCount=',FLockCount,' State = ',GetSemValue(FMutexSemaphore));
428 if FLockCount = 0 then
429 sem_op(FMutexSemaphore,-1);
430 Inc(FLockCount);
431 //writeln('Lock: Mutex Exit');
432 end;
433
434 {Give up ownership of the Mutex and allow other threads access }
435
436 procedure TMutex.Unlock;
437 begin
438 //writeln('UnLock: Entering Mutex, LockCount=',FLockCount);
439 if FLockCount = 0 then Exit;
440 Dec(FLockCount);
441 if FLockCount = 0 then
442 sem_op(FMutexSemaphore,1);
443 //writeln('UnLock: Mutex Exit',' State = ',GetSemValue(FMutexSemaphore));
444 end;
445
446 { TSingleLockGate }
447
448 function TSingleLockGate.GetWaitingThreads: integer;
449 begin
450 Result := FWaitingThreads^
451 end;
452
453 constructor TSingleLockGate.Create(SemNum: cint; AOwner: TGlobalInterface);
454 begin
455 inherited Create;
456 FOwner := AOwner;
457 FSignalledState := PInteger(FOwner.SharedMemory.Allocate(sizeof(FSignalledState)));
458 FWaitingThreads := PInteger(FOwner.SharedMemory.Allocate(sizeof(FWaitingThreads)));
459 FSemaphore := SemNum;
460 FMutex := SemNum + 1;
461 if FInitialiser then
462 begin
463 FSignalledState^ := 1;
464 FWaitingThreads^ := 0;
465 SemInit(FSemaphore,0);
466 SemInit(FMutex,1);
467 end;
468 end;
469
470 procedure TSingleLockGate.PassthroughGate;
471 begin
472 if FSignalledState^ = 0 then
473 begin
474 sem_op(FMutex,-1,0); //Acquire Mutex
475 Inc(FWaitingThreads^);
476 sem_op(FMutex,1,0); //Release Mutex
477 //writeln(ClassName + ': Wait State Entered ',FSemaphore,' = ',GetSemValue(FSemaphore));
478 sem_op(FSemaphore,-1,0); //Enter Wait
479 //writeln(ClassName + ': Wait State Ends ',FSemaphore);
480 end;
481 end;
482
483 procedure TSingleLockGate.Unlock;
484 begin
485 if FSignalledState^ = 0 then
486 begin
487 FSignalledState^ := 1;
488 sem_op(FMutex,-1,0); //Acquire Mutex
489 //writeln(ClassName + ': Unlocking' ,FSemaphore);
490 sem_op(FSemaphore,FWaitingThreads^,0);
491 FWaitingThreads^ := 0;
492 sem_op(FMutex,1,0); //Release Mutex
493 end;
494 end;
495
496 procedure TSingleLockGate.Lock;
497 begin
498 if FSignalledState^ = 1 then
499 begin
500 //writeln(ClassName + ': Locking Gate ',FSemaphore);
501 SemInit(FSemaphore,0);
502 FSignalledState^ := 0;
503 end;
504 end;
505
506 { TMultilockGate }
507
508 constructor TMultilockGate.Create(SemNum: cint; AOwner: TGlobalInterface);
509 begin
510 inherited Create;
511 FOwner := AOwner;
512 FSemaphore := SemNum;
513 FMutex := SemNum + 1;
514 FLockCount := PInteger(FOwner.SharedMemory.Allocate(sizeof(FLockCount)));
515 if FInitialiser then
516 begin
517 FLockCount^ := 0;
518 SemInit(FSemaphore,1);
519 SemInit(FMutex,1);
520 end;
521 end;
522
523 function TMultilockGate.GetLockCount: integer;
524 begin
525 Result := FLockCount^
526 end;
527
528 procedure TMultilockGate.Lock;
529 begin
530 sem_op(FMutex,-1,0); //Acquire Mutex
531 if FLockCount^ = 0 then
532 begin
533 //writeln(ClassName,': Locking ',FSemaphore);
534 SemInit(FSemaphore,0);
535 end;
536 Inc(FLockCount^);
537 sem_op(FMutex,1,0); //Release Mutex
538 end;
539
540 procedure TMultilockGate.Unlock;
541 begin
542 sem_op(FMutex,-1,0); //Acquire Mutex
543 Dec(FLockCount^);
544 if FLockCount^ <= 0 then
545 begin
546 //writeln(ClassName,': UnLocking ',FSemaphore);
547 SemInit(FSemaphore,1);
548 FLockCount^ := 0
549 end;
550 sem_op(FMutex,1,0); //Release Mutex
551 end;
552
553 procedure TMultilockGate.PassthroughGate;
554 begin
555 if FLockCount^ = 0 then
556 Exit;
557 //writeln(ClassName,': Waiting on ',FSemaphore);
558 while sem_timedop(FSemaphore,-1,cDefaultTimeout) < 0 do
559 {looks like we lost a reader}
560 begin
561 if FLockCount^ > 0 then
562 begin
563 UnLock;
564 if assigned(FOnGateTimeout) then
565 OnGateTimeout(self)
566 end
567 end;
568 sem_op(FSemaphore,1);
569 //writeln(ClassName,': Wait done on ',FSemaphore);
570 end;
571
572
573 { TGlobalInterface }
574
575 function TGlobalInterface.GetMonitorCount: integer;
576 begin
577 Result := GetSemValue(cMonitorCounter)
578 end;
579
580 constructor TGlobalInterface.Create;
581 begin
582 inherited Create;
583 FSharedMemory := TSharedMemory.Create(cMonitorHookSize);
584
585 FWriteLock := TMutex.Create(cMutexSemaphore);
586
587 FDataAvailableEvent := TSingleLockGate.Create(cDataAvailableEventSemaphore,self);
588 FWriterBusyEvent := TSingleLockGate.Create(cWriterBusyEventSemaphore,self);
589 FReadReadyEvent := TMultiLockGate.Create(cReadReadyEventSemaphore,self);
590 FReadFinishedEvent := TMultiLockGate.Create(cReadFinishedEventSemaphore,self);
591
592 if FInitialiser then
593 SemInit(cMonitorCounter,0);
594 FTraceDataType := PInteger(FSharedMemory.Allocate(sizeof(Integer)));
595 FTimeStamp := PDateTime(FSharedMemory.Allocate(sizeof(TDateTime)));
596 FBufferSize := PInteger(FSharedMemory.Allocate(sizeof(Integer)));
597 FBuffer := FSharedMemory.Allocate(0); //All remaining
598 FMaxBufferSize := FSharedMemory.LastAllocationSize;
599
600 if FInitialiser then
601 begin
602 FBufferSize^ := 0;
603 FDataAvailableEvent.Lock
604 end;
605 end;
606
607 destructor TGlobalInterface.Destroy;
608 begin
609 if assigned(FWriteLock) then FWriteLock.Free;
610 if assigned(FDataAvailableEvent) then FDataAvailableEvent.Free;
611 if assigned(FWriterBusyEvent) then FWriterBusyEvent.Free;
612 if assigned(FReadReadyEvent) then FReadReadyEvent.Free;
613 if assigned(FReadFinishedEvent) then FReadFinishedEvent.Free;
614 if assigned(FSharedMemory) then FSharedMemory.Free;
615 inherited Destroy;
616 end;
617
618 procedure TGlobalInterface.IncMonitorCount;
619 begin
620 sem_op(cMonitorCounter,1);
621 end;
622
623 procedure TGlobalInterface.DecMonitorCount;
624 begin
625 sem_op(cMonitorCounter,-1,IPC_NOWAIT);
626 end;
627
628 procedure TGlobalInterface.SendTrace(TraceObject: TTraceObject);
629 begin
630 FTraceDataType^ := Integer(TraceObject.FDataType);
631 FTimeStamp^ := TraceObject.FTimeStamp;
632 FBufferSize^ := Min(Length(TraceObject.FMsg), MaxBufferSize);
633 Move(TraceObject.FMsg[1], FBuffer^, FBufferSize^);
634 end;
635
636 procedure TGlobalInterface.ReceiveTrace(TraceObject: TTraceObject);
637 begin
638 SetString(TraceObject.FMsg, FBuffer, FBufferSize^);
639 TraceObject.FDataType := TTraceFlag(FTraceDataType^);
640 TraceObject.FTimeStamp := TDateTime(FTimeStamp^);
641 end;
642
643
644