Jump to content
Sign in to follow this  
desert_coffee

Attempt to release mutex not owned by caller

Recommended Posts

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

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 by Der schöne Günther
  • Like 2

Share this post


Link to post
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
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
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
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

  • Thanks 1

Share this post


Link to post
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 by programmerdelphi2k

Share this post


Link to post
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

"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
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

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×