Jump to content

StephenM

Members
  • Content Count

    2
  • Joined

  • Last visited

Community Reputation

0 Neutral
  1. I'm looking for best practices or guidance for the following scenario. The front end is a page control with multiple pages. The user completes the form and presses 'Save'. The app takes the contents of the controls, populates an XML document, and sends the document to a server for validation and processing. Let's say a validation error occurs. One of the XML fields contained a negative number and only positive numbers are allowed. How does the client know which page and control to highlight for a given validation? I think there needs to be some association between the data entry controls and the XML fields, but I'm unsure what is the best way to build this. Have you handled this situation before? What algorithm or data structure did you use? Regards, Stephen
  2. Hello Gabr, I'm seeing a race condition in TOmniResourceCount in the TryAllocate function. I created a logger following your advice in: https://stackoverflow.com/questions/8134526/implement-thread-safe-logging Two methods in the TOmniLogger code relevant to this discussion are: procedure TOmniLogger.Log(const Text: string); begin FLogQueue.Enqueue(Text); FLogMsgCount.Allocate; end; procedure TOmniLogger.Logger(const task: IOmniTask); var LogFile: TOmniLogFile; procedure Flush; var logData: TOmniValue; begin while FLogQueue.TryDequeue(logData) do begin FLogMsgCount.Release; LogFile.Log(logData.AsString); end; end; begin LogFile := TOmniLogFile.Create(FFileName); FTaskName := Format('%s:%s [ThreadId:%d]', [Self.ClassName, SysUtils.ExtractFileName(FFileName), Windows.GetCurrentThreadId]); try Log(Format('Task "%s" on thread [%d, %d]', [Task.Name, Task.UniqueID, Windows.GetCurrentThreadId])); while DSiWaitForTwoObjects(task.TerminateEvent, FLogMsgCount.Handle, false, CMaxLogTimeout_ms) <> WAIT_OBJECT_0 do Flush; Flush; finally LogFile.Free; end; end; FLogMsgCount is an instance of TOmniResourceCount. The race condition occurs in the tight while DSiWaitForTwoObjects loop and involves FLogMsgCount.Handle. This code calls two methods from TOmniResourceCount in OtlSync.pas, Release and TryAllocate: function TOmniResourceCount.Release: cardinal; begin orcLock.Acquire; try Result := cardinal(orcNumResources.Increment); if Result = 1 then begin ResetEvent(orcHandle); SetEvent(orcAvailable); end; finally orcLock.Release; end; end; { TOmniResourceCount.Release } ///<summary>Like Allocate, but with a timeout.</summary> function TOmniResourceCount.TryAllocate(var resourceCount: cardinal; timeout_ms: cardinal): boolean; var startTime_ms: int64; waitTime_ms : int64; begin Result := false; startTime_ms := DSiTimeGetTime64; //TODO: Rewrite this with a faster, non-locking clock orcLock.Acquire; repeat if orcNumResources.Value = 0 then begin orcLock.Release; if timeout_ms <= 0 then Exit; if timeout_ms = INFINITE then waitTime_ms := INFINITE else begin waitTime_ms := startTime_ms + timeout_ms - DSiTimeGetTime64; if waitTime_ms <= 0 then Exit; end; if WaitForSingleObject(orcAvailable, waitTime_ms) <> WAIT_OBJECT_0 then Exit; // skip final Release orcLock.Acquire; end; if orcNumResources.Value > 0 then begin Result := true; resourceCount := cardinal(orcNumResources.Decrement); if resourceCount = 0 then begin ResetEvent(orcAvailable); //reset before release - otherwise there's a race condition between this code and .Release orcLock.Release; //prevent race condition - another thread may wait on orcHandle and destroy this instance SetEvent(orcHandle); Exit; // skip final Release end; break; //repeat end; until false; orcLock.Release; end; { TOmniResourceCount.TryAllocate } Here's the scenario where I see the race condition occurring: 1) resourceCount is 0. This code executes in TryAllocate: if resourceCount = 0 then begin ResetEvent(orcAvailable); //reset before release - otherwise there's a race condition between this code and .Release orcLock.Release; //prevent race condition - another thread may wait on orcHandle and destroy this instance SetEvent(orcHandle); Exit; // skip final Release end; 2) After the orLock.Release and before the SetEvent a thread switch occurs. 3) A call to TOmniResourceCount.Allocate is made via the TOmniLogger.Log procedure above. ResetEvent(orcHandle) is called. 4) A thread switch occurs. Now SetEvent(orcHandle) in TryAllocate executes. This causes: while DSiWaitForTwoObjects(task.TerminateEvent, FLogMsgCount.Handle, false, CMaxLogTimeout_ms) <> WAIT_OBJECT_0 do Flush; To execute. The end result is FLogMsgCount.Handle is stuck in the signaled state and this loop takes over a core. My workaround is to move the SetEvent(orcHandle) into the critical section (before orcLock.Release in TryAllocate), thus preventing this scenario. Does this seem like the correct approach to you? Regards, Stephen
×