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

Fixed esoteric bugs in VideoPreviewWidget and VideoFrameExtractor and added some features #922

Merged
merged 2 commits into from
Jul 12, 2017

Conversation

cculianu
Copy link
Contributor

@cculianu cculianu commented Jul 6, 2017

Hi,

This pull request is to avoid some occasional hangs/bugs with VideoPreviewWidget (and VideoFrameExtractor).

  1. VideoPreviewWidget/VideoFrameExtractor would sometimes hang because there was a race condition in the task.take() call in addTask(). In order to fix this I needed to add an optional timeout in BlockingQueue -- if task.take() times out, it's ok -- it means another thread took the task.

  2. VideoPreviewWidget failed to scale frames properly when videoFormat == m_out->videoFormat. As a result, preview frames looked wrong.

  3. VideoPreviewWidget had some class methods declared for keepAspectRatio but they weren't implemented. Fixed.

Additionally, I cleaned up the code in VideoFrameExtractor and fixed it to be error-free. I got rid of the ASYNC_EVENT and ASYNC_SIGNAL mechanisms (which were disabled anyway at compile-time) because they were inferior/redundant with ASYNC_TASK.

I also fixed the warning about "moveToThread on object with a parent". The VideoFrameExtractor doesn't need to live in d.thread -- I see no advantage to this and it can only cause problems, so I got rid of the call.

This is heavily tested and I am fairly certain no new bugs were introduced.

Wherever I changed a shared API (such as BlockingQueue) -- I made sure that the 'defaults' to the methods behave exactly 100% the same as the old methods. I just added optional parameters for timeouts. If not using the optional parameters, methods have exactly the same behavior (including ability to put() onto an already-full queue!).

Thanks so much and let me know if there are any issues or comments on this PR.

@wang-bin
Copy link
Owner

Why lock the mutex in setPosition/position, setSource/source etc.?

@cculianu
Copy link
Contributor Author

cculianu commented Jul 10, 2017

Because these variables are potentially modified and read in different threads.

setSource -- because even though QStrings are implicitly shared, Qt docs say not to write/read from the same instance of the object from multiple threads. It may be paranoia but according to Qt docs writing to d.source from one thread while another thread is reading d.source is not supported (I never actually checked in the Qt sourcecode to determine if it would work and is safe, though -- is it?).

setPosition -- because that variable is shared across threads and I believe qint64 writes aren't atomic on all architectures -- are they?

setPrecision -- can probably do away with it since I think int reads/writes are atomic on all architectures..

@wang-bin
Copy link
Owner

Qt docs say not to write/read from the same instance of the object from multiple threads. It may be paranoia but according to Qt docs writing to d.source from one thread while another thread is reading d.source is not supported

Wrong. If it's right, what is implicitly sharing used for? string assignment will delete the old data if reference is 0, or just replace the data ptr and the reader still keep old data ptr.

setPosition -- because that variable is shared across threads and I believe qint64 writes aren't atomic on all architectures -- are they?

No one else reads position value, just user. the position value is set in task object.

setPrecision -- can probably do away with it since I think int reads/writes are atomic on all architectures..

It's rarely changed by user when running a task

@cculianu
Copy link
Contributor Author

Wrong. If it's right, what is implicitly sharing used for?

Implicitly shared is still useful -- aside from the benefit you get with reference counting, thread-wise it is indeed thread-safe if you are holding two instances to the same underlying object. The implementation of copy-on-write is thread-safe provided the two wrapper instances are owned by different threads, etc.

However according to Qt docs not all objects can safely be written-to or modified if two different threads are accessing them.

I double-checked the implementation of QString and it at least appears to be safe for sharing across threads due to the way it updates its data and pointers in copy-on-write.

Classes such as QMap and QList however aren't safe to share across threads if you keep a handle to the same instance (and I think I ran into this bug in one of my other PRs, possibly).

So:

QList<int> a, b(a);

// ^^ both a and b above point to the same reference counted private implementation object

Thread 1: a.push_back(1); 
Thread 2: a.push_back(2); // unsafe if thread1 happens to access a at the same time.
Thread 2: b.push_back(2); // safe since reference counting and detach, etc of copy-on-write is implemented atomically

Anyway.. it appears you are right about QString though, and in the interests of trusting your expertise, I will go ahead and get rid of the locks.

I will go ahead and revise the code and remove the mutex as per your concerns and push another commit. Hopefully 2 commits in 1 PR is ok with you.

@cculianu
Copy link
Contributor Author

I pushed the changes you suggested -- if you decide to accept this PR (please do as it does fix a real hang bug!) -- let me know if you want me to squash the two commits into 1 commit.

Best regards!

@wang-bin
Copy link
Owner

you are right. it's safe only if accessing a copy. You don't need to add a lock in setter/getter because qt doesn't do it.

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.

2 participants