desert_coffee 0 Posted December 6, 2022 Hello, I'm trying to recreate the c++ lock_guard with this naive implementation TLockGuard = class(TInterfacedObject) private var _mtx: TMutex; _name: String; public constructor Create(mtx: TMutex; name: String = ''); destructor Destroy; override; end; constructor TLockGuard.Create(mtx: TMutex; name: String); begin inherited Create; _mtx := mtx; _name := name; if assigned(mtx) then begin loginfo(format('acquiring mutex %s', [_name]), 'thread'); _mtx.Acquire; loginfo(format('acquired mutex %s', [_name]), 'thread'); end else raise Exception.Create('mutex unassigned'); end; destructor TLockGuard.Destroy; begin if assigned(_mtx) then begin loginfo(format('releasing mutex %s', [_name]), 'thread'); try _mtx.Release; except on E: Exception do loginfo(E.toString, 'thread'); end; loginfo(format('released mutex %s', [_name]), 'thread'); end; inherited Destroy; end; but when Destroy is called and it tries to release the mutex I get this error: Quote System Error. Code: 288. Attempt to release mutex not owned by caller Is there a way to move ownership of the mutex? Share this post Link to post
Der schöne Günther 316 Posted December 7, 2022 (edited) Hi there. TMutex.Release() is just calling ReleaseMutex(..) of the regular Win32 API. Here is its documentation: ReleaseMutex function (synchapi.h) - Win32 apps | Microsoft Learn Quote However, to release its ownership, the thread must call ReleaseMutex one time for each time that it obtained ownership (either through CreateMutex or a wait function). You acquired the mutex directly in the constructor of TLockGuard. You will have to release it from the same thread that acquired it. By the way: In your destructor you are calling _mtx.Release() even if _mtx is nil By the way: Can you post a complete example of how you are using your TLockGuard? For simple cases I have not been able to reproduce your issue. If you are constructing it locally, reference it as an IInterface and do not pass it somewhere else, it should be fine ... I guess. Edited December 7, 2022 by Der schöne Günther 2 Share this post Link to post
programmerdelphi2k 237 Posted December 7, 2022 constructor Create(mtx: TMutex; name: String = ''); the "obj" Mutex is not been created on this class, but only "Acquired", then needs "free or release" on the "origin" not in this class! I believe that would be this: type TLockGuard = class(TInterfacedObject) private FMutex: TMutex; public constructor Create; destructor Destroy; override; // procedure MyTaskNeedsAMutex; end; { TLockGuard } constructor TLockGuard.Create; begin FMutex := TMutex.Create(); end; destructor TLockGuard.Destroy; begin FMutex.Free; inherited; end; procedure TLockGuard.MyTaskNeedsAMutex; begin FMutex.Acquire; try // do the task finally FMutex.Release; end; end; Share this post Link to post
David Heffernan 2345 Posted December 7, 2022 5 minutes ago, programmerdelphi2k said: constructor Create(mtx: TMutex; name: String = ''); the "obj" Mutex is not been created on this class, but only "Acquired", then needs "free or release" on the "origin" not in this class! I believe that would be this: type TLockGuard = class(TInterfacedObject) private FMutex: TMutex; public constructor Create; destructor Destroy; override; // procedure MyTaskNeedsAMutex; end; { TLockGuard } constructor TLockGuard.Create; begin FMutex := TMutex.Create(); end; destructor TLockGuard.Destroy; begin FMutex.Free; inherited; end; procedure TLockGuard.MyTaskNeedsAMutex; begin FMutex.Acquire; try // do the task finally FMutex.Release; end; end; The asker wants to use reference counting with a mutex that is created elsewhere, and passed to the guard object. Your code does something completely different from what the asker wants to do. Share this post Link to post
desert_coffee 0 Posted December 7, 2022 6 hours ago, Der schöne Günther said: Hi there. TMutex.Release() is just calling ReleaseMutex(..) of the regular Win32 API. Here is its documentation: ReleaseMutex function (synchapi.h) - Win32 apps | Microsoft Learn You acquired the mutex directly in the constructor of TLockGuard. You will have to release it from the same thread that acquired it. By the way: In your destructor you are calling _mtx.Release() even if _mtx is nil By the way: Can you post a complete example of how you are using your TLockGuard? For simple cases I have not been able to reproduce your issue. If you are constructing it locally, reference it as an IInterface and do not pass it somewhere else, it should be fine ... I guess. The problem was that I was acquiring the mutex in a separate thread and deliberately freeing it within a main thread synchronize block. Also through some trial and error I realize that the TLockGuard only frees itself when assigned to an IInterface variable. Thanks for your help. I've added a check within a critical section that makes sure that _mtx is non-nil when acquiring and releasing. Share this post Link to post
David Heffernan 2345 Posted December 7, 2022 6 minutes ago, desert_coffee said: Also through some trial and error I realize that the TLockGuard only frees itself when assigned to an IInterface variable. This is a very long standing bug that for reasons I cannot understand Embarcadero won't fix 1 Share this post Link to post
programmerdelphi2k 237 Posted December 7, 2022 (edited) 3 hours ago, desert_coffee said: TLockGuard only frees itself when assigned to an IInterface variable. if your new class inherited from "TInterfacedObject" defined as: => class(TObject, IInterface), then, would necessary or: add a new custom-interface (your) on class definition: TLockGuard = class(TInterfacedObject, IYourInterfaceCustom) or use a default, like IInterface, maybe "IUnKnown" then, you can just nil'ED to call the "destruction" Edited December 7, 2022 by programmerdelphi2k Share this post Link to post
David Heffernan 2345 Posted December 7, 2022 17 minutes ago, programmerdelphi2k said: if your new class inherited from "TInterfacedObject" defined as: => class(TObject, IInterface), then, would necessary or: add a new custom-interface (your) on class definition: TLockGuard = class(TInterfacedObject, IYourInterfaceCustom) or use a default, like IInterface, maybe "IUnKnown" then, you can just nil'ED to call the "destruction" No. The asker is talking about the known bug where you pass TInterfacedObject.Create to a const arg and the object is never destroyed. Share this post Link to post
programmerdelphi2k 237 Posted December 7, 2022 "TLockGuard = class( TInterfacedObject)" is not a "interface at all", but a obj (type TObject) interfaced (using IInterface as its ref-counter)! then, when creating a new obj based on "TInterfacedObject" class, you'll get a "TObject interfaced", and needs define when destroy it... using "FREE" is the variable was defined as "var X : TLockGuard". Else, if it was defined as "var X : IInterface", then, the refcount into in action! where "nil" will go decrement its ref-counter! implementation {$R *.dfm} type TMyHack = class(TInterfacedObject); type TLockGuard = class(TInterfacedObject) end; var ALock: IInterface; // } TLockGuard; procedure TForm1.FormCreate(Sender: TObject); begin ALock := TLockGuard.Create; // Caption := TMyHack(TInterfacedObject(ALock)).RefCount.ToString; // 1=Interface, 0=class end; initialization ReportMemoryLeaksOnShutdown := true; finalization ALock := nil; // based on a Interface // ALock.Free; // based on a class Share this post Link to post
David Heffernan 2345 Posted December 7, 2022 3 minutes ago, programmerdelphi2k said: "TLockGuard = class( TInterfacedObject)" is not a "interface at all", but a obj (type TObject) interfaced (using IInterface as its ref-counter)! then, when creating a new obj based on "TInterfacedObject" class, you'll get a "TObject interfaced", and needs define when destroy it... using "FREE" is the variable was defined as "var X : TLockGuard". Else, if it was defined as "var X : IInterface", then, the refcount into in action! where "nil" will go decrement its ref-counter! implementation {$R *.dfm} type TMyHack = class(TInterfacedObject); type TLockGuard = class(TInterfacedObject) end; var ALock: IInterface; // } TLockGuard; procedure TForm1.FormCreate(Sender: TObject); begin ALock := TLockGuard.Create; // Caption := TMyHack(TInterfacedObject(ALock)).RefCount.ToString; // 1=Interface, 0=class end; initialization ReportMemoryLeaksOnShutdown := true; finalization ALock := nil; // based on a Interface // ALock.Free; // based on a class We are talking about the bug described here https://stackoverflow.com/a/7640979/505088 Share this post Link to post
programmerdelphi2k 237 Posted December 7, 2022 and me, dont talk with you! ... just these time! Share this post Link to post