Bug 150807

Summary: [GStreamer] Use RunLoop::Timer instead of GMainLoopSource in video sink
Product: WebKit Reporter: Carlos Garcia Campos <cgarcia>
Component: PlatformAssignee: Nobody <webkit-unassigned>
Status: RESOLVED FIXED    
Severity: Normal CC: pnormand, zan
Priority: P2 Keywords: Gtk
Version: WebKit Local Build   
Hardware: Unspecified   
OS: Unspecified   
Attachments:
Description Flags
Patch
none
Updated patch
none
Updated patch zan: review+

Description Carlos Garcia Campos 2015-11-02 10:34:43 PST
We could use RunLoop::Timer here, since we always wait until the smaple is actually rendered we don't really need either a thread safe main loop source, nor cancelling if already requested and other things GMainLoopSource does.
Comment 1 Carlos Garcia Campos 2015-11-02 10:38:30 PST
Created attachment 264600 [details]
Patch
Comment 2 Darin Adler 2015-11-02 22:30:23 PST
Comment on attachment 264600 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=264600&action=review

> Source/WebCore/ChangeLog:8
> +        Since we always wait until the smaple is actually rendered we

sample is misspelled here

> Source/WebCore/platform/graphics/gstreamer/VideoSinkGStreamer.cpp:78
> +        // Use a higher priority than WebCore timers (G_PRIORITY_HIGH_IDLE + 20).
> +        m_timer.setPriority(G_PRIORITY_HIGH_IDLE + 19);

This code is compiled for EFL, too. But this code is glib-specific. Is that OK for that port? Compile failed.

> Source/WebCore/platform/graphics/gstreamer/VideoSinkGStreamer.cpp:140
> +struct _WebKitVideoSinkPrivate {

This peculiar use of a leading underscore followed by a capital letter trespasses on namespace that is reserved for compiler implementation, I believe.
Comment 3 Carlos Garcia Campos 2015-11-02 23:23:18 PST
(In reply to comment #2)
> Comment on attachment 264600 [details]
> Patch
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=264600&action=review
> 
> > Source/WebCore/ChangeLog:8
> > +        Since we always wait until the smaple is actually rendered we
> 
> sample is misspelled here

Oops. I'll fix it.

> > Source/WebCore/platform/graphics/gstreamer/VideoSinkGStreamer.cpp:78
> > +        // Use a higher priority than WebCore timers (G_PRIORITY_HIGH_IDLE + 20).
> > +        m_timer.setPriority(G_PRIORITY_HIGH_IDLE + 19);
> 
> This code is compiled for EFL, too. But this code is glib-specific. Is that
> OK for that port? Compile failed.

Yes, I forgot to add platform ifdef there.

> > Source/WebCore/platform/graphics/gstreamer/VideoSinkGStreamer.cpp:140
> > +struct _WebKitVideoSinkPrivate {
> 
> This peculiar use of a leading underscore followed by a capital letter
> trespasses on namespace that is reserved for compiler implementation, I
> believe.

This is the normal way to declare the structs in GObject. They are forward declared as typedef struct _Foo Foo; and then struct _Foo { }
Comment 4 Zan Dobersek 2015-11-03 00:43:58 PST
Comment on attachment 264600 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=264600&action=review

> Source/WebCore/platform/graphics/gstreamer/VideoSinkGStreamer.cpp:90
> +        m_timer.stop();
> +        LockHolder locker(m_sampleMutex);

Any reason behind this ordering? The m_timer is being operated on under a lock in requestRender().

> Source/WebCore/platform/graphics/gstreamer/VideoSinkGStreamer.cpp:145
> +        if (currentCaps)
> +            gst_caps_unref(currentCaps);

Why is this being dereferenced in the constructor?
Comment 5 Carlos Garcia Campos 2015-11-03 01:22:44 PST
(In reply to comment #4)
> Comment on attachment 264600 [details]
> Patch
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=264600&action=review
> 
> > Source/WebCore/platform/graphics/gstreamer/VideoSinkGStreamer.cpp:90
> > +        m_timer.stop();
> > +        LockHolder locker(m_sampleMutex);
> 
> Any reason behind this ordering? The m_timer is being operated on under a
> lock in requestRender().

Tried to follow what previous code did. We don't need the lock to protect the timer I think

> > Source/WebCore/platform/graphics/gstreamer/VideoSinkGStreamer.cpp:145
> > +        if (currentCaps)
> > +            gst_caps_unref(currentCaps);
> 
> Why is this being dereferenced in the constructor?

Oh, good catch, that was supposed to be in the destructor :-P
Comment 6 Carlos Garcia Campos 2015-11-03 02:49:31 PST
Created attachment 264681 [details]
Updated patch
Comment 7 Zan Dobersek 2015-11-03 22:36:33 PST
Comment on attachment 264681 [details]
Updated patch

View in context: https://bugs.webkit.org/attachment.cgi?id=264681&action=review

> Source/WebCore/platform/graphics/gstreamer/VideoSinkGStreamer.cpp:92
> +        m_timer.stop();
> +        LockHolder locker(m_sampleMutex);

I'm still stuck on this. in requestRender() m_timer operation is protected, here it's not.

Regarding previous behavior, timeoutSource was never cancelled after scheduling, except when WebKitVideoSinkPrivate was destroyed. This technically deviates from that.
Comment 8 Carlos Garcia Campos 2015-11-03 22:55:50 PST
(In reply to comment #7)
> Comment on attachment 264681 [details]
> Updated patch
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=264681&action=review
> 
> > Source/WebCore/platform/graphics/gstreamer/VideoSinkGStreamer.cpp:92
> > +        m_timer.stop();
> > +        LockHolder locker(m_sampleMutex);
> 
> I'm still stuck on this. in requestRender() m_timer operation is protected,
> here it's not.

It's not actually the timer what is protected, but the sample, we need to hold the lock for the condition wait. 

> Regarding previous behavior, timeoutSource was never cancelled after
> scheduling, except when WebKitVideoSinkPrivate was destroyed. This
> technically deviates from that.

That's right, but I think it was not desirable. We always wait until the timer is actually fired. That means that if unlockSampleMutex() was called for whatever reason before the timer is fired, the sample is destroyed and the condition was released, so we don't keep waiting anymore. But the timer will be fired anyway, but will return early because sample is NULL (and unlocked = true). Stopping the timer we just avoid the callback to be called just to return.
Comment 9 Zan Dobersek 2015-11-03 23:01:08 PST
Comment on attachment 264681 [details]
Updated patch

View in context: https://bugs.webkit.org/attachment.cgi?id=264681&action=review

>>> Source/WebCore/platform/graphics/gstreamer/VideoSinkGStreamer.cpp:92
>>> +        LockHolder locker(m_sampleMutex);
>> 
>> I'm still stuck on this. in requestRender() m_timer operation is protected, here it's not.
>> 
>> Regarding previous behavior, timeoutSource was never cancelled after scheduling, except when WebKitVideoSinkPrivate was destroyed. This technically deviates from that.
> 
> It's not actually the timer what is protected, but the sample, we need to hold the lock for the condition wait.

The timer is protected in requestRender(), and I don't see any reason why it wouldn't be protected here.
Comment 10 Carlos Garcia Campos 2015-11-03 23:05:58 PST
(In reply to comment #9)
> Comment on attachment 264681 [details]
> Updated patch
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=264681&action=review
> 
> >>> Source/WebCore/platform/graphics/gstreamer/VideoSinkGStreamer.cpp:92
> >>> +        LockHolder locker(m_sampleMutex);
> >> 
> >> I'm still stuck on this. in requestRender() m_timer operation is protected, here it's not.
> >> 
> >> Regarding previous behavior, timeoutSource was never cancelled after scheduling, except when WebKitVideoSinkPrivate was destroyed. This technically deviates from that.
> > 
> > It's not actually the timer what is protected, but the sample, we need to hold the lock for the condition wait.
> 
> The timer is protected in requestRender(), and I don't see any reason why it
> wouldn't be protected here.

It's protected only because it needs to happen between changing the sample and the condition wait. In stop() we can simply cancel the timer before waiting to acquire the lock. I can move it inside the protection if you think it's a problem
Comment 11 Zan Dobersek 2015-11-03 23:25:54 PST
Comment on attachment 264681 [details]
Updated patch

View in context: https://bugs.webkit.org/attachment.cgi?id=264681&action=review

> Source/WebCore/platform/graphics/gstreamer/VideoSinkGStreamer.cpp:180
>  static GstFlowReturn webkitVideoSinkRender(GstBaseSink* baseSink, GstBuffer* buffer)
>  {
>      WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(baseSink);
>      WebKitVideoSinkPrivate* priv = sink->priv;
> -
> -    WTF::GMutexLocker<GMutex> lock(priv->sampleMutex);
> -
> -    if (priv->unlocked)
> +    if (priv->scheduler.isUnlocked())
>          return GST_FLOW_OK;

This is another problem.

Before, the sampleMutex was locked throughout this function. Now it's only locked when querying the unlocked status of the scheduler, and when requesting the render callback. So this now requires two locks per webkitVideoSinkRender() invocation, and it also allows for the scheduler state to change during these locks.

I would expose a `LockHolder lock();` on the VideoRenderRequestScheduler interface, and the have the public methods accept LockHolder& as their first parameter. That way the caller is in complete control of the scope of the lock.
(Inspired by bmalloc: http://trac.webkit.org/browser/trunk/Source/bmalloc/bmalloc/Heap.h#L57)
Comment 12 Carlos Garcia Campos 2015-11-04 00:05:18 PST
(In reply to comment #11)
> Comment on attachment 264681 [details]
> Updated patch
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=264681&action=review
> 
> > Source/WebCore/platform/graphics/gstreamer/VideoSinkGStreamer.cpp:180
> >  static GstFlowReturn webkitVideoSinkRender(GstBaseSink* baseSink, GstBuffer* buffer)
> >  {
> >      WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(baseSink);
> >      WebKitVideoSinkPrivate* priv = sink->priv;
> > -
> > -    WTF::GMutexLocker<GMutex> lock(priv->sampleMutex);
> > -
> > -    if (priv->unlocked)
> > +    if (priv->scheduler.isUnlocked())
> >          return GST_FLOW_OK;
> 
> This is another problem.
> 
> Before, the sampleMutex was locked throughout this function. Now it's only
> locked when querying the unlocked status of the scheduler, and when
> requesting the render callback. So this now requires two locks per
> webkitVideoSinkRender() invocation, and it also allows for the scheduler
> state to change during these locks.
> 
> I would expose a `LockHolder lock();` on the VideoRenderRequestScheduler
> interface, and the have the public methods accept LockHolder& as their first
> parameter. That way the caller is in complete control of the scope of the
> lock.
> (Inspired by bmalloc:
> http://trac.webkit.org/browser/trunk/Source/bmalloc/bmalloc/Heap.h#L57)

hmm, the mutex is supposed to protect the sample and unlocked, and non of those change between the isUnlocked and requestRender in that function. However, you are right that the scheduler state could change. Wouldn't it be better to takes that into account instead and return early from requestRender if sample is NULL? In the case stop() is called before requestRender(), if we have the lock, the timer will start and right after we wait, the scheduler will stop. If we don't have the lock, we don't even start the timer.
Comment 13 Carlos Garcia Campos 2015-11-04 00:07:20 PST
(In reply to comment #12)
> (In reply to comment #11)
> > Comment on attachment 264681 [details]
> > Updated patch
> > 
> > View in context:
> > https://bugs.webkit.org/attachment.cgi?id=264681&action=review
> > 
> > > Source/WebCore/platform/graphics/gstreamer/VideoSinkGStreamer.cpp:180
> > >  static GstFlowReturn webkitVideoSinkRender(GstBaseSink* baseSink, GstBuffer* buffer)
> > >  {
> > >      WebKitVideoSink* sink = WEBKIT_VIDEO_SINK(baseSink);
> > >      WebKitVideoSinkPrivate* priv = sink->priv;
> > > -
> > > -    WTF::GMutexLocker<GMutex> lock(priv->sampleMutex);
> > > -
> > > -    if (priv->unlocked)
> > > +    if (priv->scheduler.isUnlocked())
> > >          return GST_FLOW_OK;
> > 
> > This is another problem.
> > 
> > Before, the sampleMutex was locked throughout this function. Now it's only
> > locked when querying the unlocked status of the scheduler, and when
> > requesting the render callback. So this now requires two locks per
> > webkitVideoSinkRender() invocation, and it also allows for the scheduler
> > state to change during these locks.
> > 
> > I would expose a `LockHolder lock();` on the VideoRenderRequestScheduler
> > interface, and the have the public methods accept LockHolder& as their first
> > parameter. That way the caller is in complete control of the scope of the
> > lock.
> > (Inspired by bmalloc:
> > http://trac.webkit.org/browser/trunk/Source/bmalloc/bmalloc/Heap.h#L57)
> 
> hmm, the mutex is supposed to protect the sample and unlocked, and non of
> those change between the isUnlocked and requestRender in that function.
> However, you are right that the scheduler state could change. Wouldn't it be
> better to takes that into account instead and return early from
> requestRender if sample is NULL?

We should actually check if m_unlocked is true, instead.

> In the case stop() is called before
> requestRender(), if we have the lock, the timer will start and right after
> we wait, the scheduler will stop. If we don't have the lock, we don't even
> start the timer.
Comment 14 Zan Dobersek 2015-11-04 00:30:51 PST
Comment on attachment 264681 [details]
Updated patch

View in context: https://bugs.webkit.org/attachment.cgi?id=264681&action=review

>>>> Source/WebCore/platform/graphics/gstreamer/VideoSinkGStreamer.cpp:180
>>>>          return GST_FLOW_OK;
>>> 
>>> This is another problem.
>>> 
>>> Before, the sampleMutex was locked throughout this function. Now it's only locked when querying the unlocked status of the scheduler, and when requesting the render callback. So this now requires two locks per webkitVideoSinkRender() invocation, and it also allows for the scheduler state to change during these locks.
>>> 
>>> I would expose a `LockHolder lock();` on the VideoRenderRequestScheduler interface, and the have the public methods accept LockHolder& as their first parameter. That way the caller is in complete control of the scope of the lock.
>>> (Inspired by bmalloc: http://trac.webkit.org/browser/trunk/Source/bmalloc/bmalloc/Heap.h#L57)
>> 
>> hmm, the mutex is supposed to protect the sample and unlocked, and non of those change between the isUnlocked and requestRender in that function. However, you are right that the scheduler state could change. Wouldn't it be better to takes that into account instead and return early from requestRender if sample is NULL? In the case stop() is called before requestRender(), if we have the lock, the timer will start and right after we wait, the scheduler will stop. If we don't have the lock, we don't even start the timer.
> 
> We should actually check if m_unlocked is true, instead.

The sample and unlock don't change during this function, or rather they're not changed directly by us. It's still entirely possible for some other thread to acquire the lock during this function call. 

All in all, having the lock out of control during this function now definitely changes behavior. I'm not fine with that.
Comment 15 Carlos Garcia Campos 2015-11-04 00:35:07 PST
(In reply to comment #14)
> Comment on attachment 264681 [details]
> Updated patch
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=264681&action=review
> 
> >>>> Source/WebCore/platform/graphics/gstreamer/VideoSinkGStreamer.cpp:180
> >>>>          return GST_FLOW_OK;
> >>> 
> >>> This is another problem.
> >>> 
> >>> Before, the sampleMutex was locked throughout this function. Now it's only locked when querying the unlocked status of the scheduler, and when requesting the render callback. So this now requires two locks per webkitVideoSinkRender() invocation, and it also allows for the scheduler state to change during these locks.
> >>> 
> >>> I would expose a `LockHolder lock();` on the VideoRenderRequestScheduler interface, and the have the public methods accept LockHolder& as their first parameter. That way the caller is in complete control of the scope of the lock.
> >>> (Inspired by bmalloc: http://trac.webkit.org/browser/trunk/Source/bmalloc/bmalloc/Heap.h#L57)
> >> 
> >> hmm, the mutex is supposed to protect the sample and unlocked, and non of those change between the isUnlocked and requestRender in that function. However, you are right that the scheduler state could change. Wouldn't it be better to takes that into account instead and return early from requestRender if sample is NULL? In the case stop() is called before requestRender(), if we have the lock, the timer will start and right after we wait, the scheduler will stop. If we don't have the lock, we don't even start the timer.
> > 
> > We should actually check if m_unlocked is true, instead.
> 
> The sample and unlock don't change during this function, or rather they're
> not changed directly by us. It's still entirely possible for some other
> thread to acquire the lock during this function call. 

Yes, that's whay I think we can handle that case by simply returning early from requestRender() if the state changed by another thread.

> All in all, having the lock out of control during this function now
> definitely changes behavior. I'm not fine with that.

Ok, I agree this is probably out of scope of this bug that is supposed to be only a refactoring.
Comment 16 Carlos Garcia Campos 2015-11-04 02:39:38 PST
Created attachment 264784 [details]
Updated patch

Ensure the lock is held while request rendering
Comment 17 Zan Dobersek 2015-11-04 23:00:04 PST
Comment on attachment 264784 [details]
Updated patch

Looks OK, I'll leave it to Phil to r+.
Comment 18 Philippe Normand 2015-11-05 00:06:30 PST
Comment on attachment 264784 [details]
Updated patch

View in context: https://bugs.webkit.org/attachment.cgi?id=264784&action=review

> Source/WebCore/platform/graphics/gstreamer/VideoSinkGStreamer.cpp:78
> +#if PLATFORM(GTK)

USE(GLIB) && !PLATFORM(EFL) please :)
Comment 19 Zan Dobersek 2015-11-05 00:06:53 PST
Comment on attachment 264784 [details]
Updated patch

r=me if Phil doesn't object.
Comment 20 Carlos Garcia Campos 2015-11-05 23:04:37 PST
(In reply to comment #18)
> Comment on attachment 264784 [details]
> Updated patch
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=264784&action=review
> 
> > Source/WebCore/platform/graphics/gstreamer/VideoSinkGStreamer.cpp:78
> > +#if PLATFORM(GTK)
> 
> USE(GLIB) && !PLATFORM(EFL) please :)

I don't think this is correct in this particular case. It's true that setPriority  is defined that way in WTF, but here there's also the priority set that only makes sense in GTK+, because of the MainThreadSharedTimerGtk implementation and the priorities used by GTK+ itself. This behaviour is very GTK+ specific
Comment 21 Carlos Garcia Campos 2015-11-05 23:29:03 PST
Committed r192094: <http://trac.webkit.org/changeset/192094>