Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: DIOS-7656 AsyncSafe might be called when the object is in destruction or construction #390

Open
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

harryz2000
Copy link
Contributor

Due to race condition, the object passing to AsyncSafe might be deleted. This fix is to avoid assertion when if it is expired after construction. We still assert to ensure the object is constructed to avoid misuse, for example. calling AsyncSafe from constructor.

@murillo128
Copy link
Member

If the object is deleted, then wouldn't the initialized value also be invalid?

@harryz2000 harryz2000 changed the title fix: DIOS-7656 AsyncSafe might be called after the object is deleted fix: DIOS-7656 AsyncSafe might be called when the object is in destruction or construction Nov 18, 2024
@harryz2000
Copy link
Contributor Author

If the object is deleted, then wouldn't the initialized value also be invalid?

The object's OnRTP shouldn't be referenced if it is after destruction, in that case the behaviour would be undefined.

This fix only works when the OnRTP was called during the destruction where initialized is still valid in destructor.

If the object is already freed. Both the timeservice and initialized would be invalid. We have to prevent that from happening. I don't see that could happen yet. As we should be calling Stop() first and then release the object.

After another thinking, to be safer, I would add destruction code in RTPIncomingMediaStreamMultiplexer class to call stop() during destruction. That should help to avoid this issue.

void Stop();
private:
RTPIncomingMediaStream::shared incomingMediaStream;
std::set<RTPIncomingMediaStream::Listener*> listeners;
volatile bool muted = false;

bool stopped = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we set initialized = false instead of a second bool? Will also assert/protect against using the object in async calls after we have stopped it.

The stop currently assumes that it is the last thing executed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand two bools seems duplicated. However, I am keen to separate the logic in the concrete class from the base TimeServiceWrapper class. The initialized was created for check whether it is constructed to avoid misuse of TimeServiceWrapper. I feel it would make it complicated to expose it to child classes.

I am current investigating a destruction issue related to this. I would do a few more tweaks around that. Will resend it for review once it's done.

@harryz2000
Copy link
Contributor Author

I further tweaked the TimeServiceWrapper to make it has a state to indicate when the Sync/Async could be called. It is found in some cases, the dependency such a TimeService passed to MediaFrameListenerBridge might be freed before MediaFrameListenerBridge is deallocated. In that case, whenever the timeService is used, it would cause segment failure. The new added state "Stopped" would prevent this happening by not allow to use timeService after this state.

I also keep the stopped member in the concrete class. I feel that make it clearer as it separates the class logic and the TimerServiceWrapper implementation. I changed it to a atomic to make it more robust.

I would check other classes that using TimeServiceWrapper as similar classes should also needed to have this "Stopped" handling. But that could be in a separate PR though.

@murillo128
Copy link
Member

I still don't understand the rationale of this PR. What issue are we trying to solve? can you put a concrete example?

@harryz2000
Copy link
Contributor Author

I still don't understand the rationale of this PR. What issue are we trying to solve? can you put a concrete example?

Sure. Yes. It is a bit tricky. I will create a more detailed description about this change shortly.

obj->OnCreated();
return obj;
}


TimeServiceWrapper(TimeService& timeService) : timeService(timeService)
{
SetTimeServiceWrapperState(WrapperState::Destructed);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

initial state shouldn't be Destructed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The was wrong indeed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed.

@@ -141,7 +141,7 @@ class MediaFrameListenerBridge :
std::chrono::milliseconds dispatchingDelayMs = std::chrono::milliseconds(0);
std::chrono::milliseconds maxDispatchingDelayMs = std::chrono::milliseconds(MaxDispatchingDelayMs);

bool stopped = false;
std::atomic_bool stopped { false };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need this atomic? we can set the stopped flag inisde the Sync scope instead

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using flag in Sync call could cause the Sync call be reentered if the previous Stop() is not finished. i.e.

if (stopped) return;
<-------- If another Stop() call happens here, the following code would be executed twice.
Sync({
....
stopped = true;
});

But if we use atomic_bool, as it set stopped immediately, it would guarantee the Sync() code wouldn't be called twice.

Uninitialized,
Initialized,
Stopped,
Destructed
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

destructed has no sense

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. It is only to distinguish with other states. I am OK to remove.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

Destructed
};

inline void SetTimeServiceWrapperState(WrapperState timeServiceWrapperState)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't want the caller code to change the state to a wrong state, can we make it private? Not sure if it could be called from the factory method.

Should be better to add a

inline void Stop()
{
SetState(Stopped);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I thought of that. It is just I find using Stop() might have name conflict with existing code. I may create a StopTimeServiceWrapper() function for that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants