-
Notifications
You must be signed in to change notification settings - Fork 501
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
add AsyncProgressQueueWorker to queue and deliver all events #692
Conversation
Overall it looks fine to me. This particular issue has been brought up on several occasions, so it would be good to have a correct example of an implementation with a queue. |
9d3191e
to
243565c
Compare
Thanks for the feedback! Tests are passing on appveyor and on travis / linux, but I had to work around some different OSX behavior in the tests where the destructor of the class got called before the main thread had a chance to call the first callback.... It's all good now (tested locally on my own mac) but I may have tied up Travis CI for the next few hours with older commits. |
374d20a
to
41f04f1
Compare
After staring at these templates so much these past few days to make the tests pass, I've decided that I'm trying to separate the I'll be away for the next few days so this will have to wait until I get back. |
2d547e9
to
cacd3d5
Compare
I'm not going to handle the separation of the I did go ahead and rename the classes from Tests have been added for both Travis CI has been flakey for some reason, but all tests should be passing correctly. Documentation has been added to explain the differences between |
ping @kkoopa ... I know github's notifications aren't the best so here's a bump. |
I'll take a look tomorrow and see if I spot anything. |
As far as I can tell, this looks good. I would however like to ask @bnoordhuis to please take a look regarding the locking and thread safety. |
Alright, nevermind then
…On September 24, 2017 4:26:34 PM GMT+03:00, Michael Ira Krufky ***@***.***> wrote:
mkrufky commented on this pull request.
> @@ -58,10 +58,14 @@
# pragma warning( disable : 4530 )
# include <string>
# include <vector>
+# include <queue>
+# include <utility>
I moved `<utility>` to the unconditional include section, but `<queue>`
actually does generate warning 4530
|
ed08d51
to
f234d15
Compare
I just added a patch that makes sure that This might be useful for Making sure that Otherwise, we run the risk that |
de3c75a
to
3fa8441
Compare
@bnoordhuis , can you please take a look regarding the locking and thread safety? |
I'll add it to my TBR (To Be Reviewed) list but at +341 −43 it's a bit too big to review right now. |
18478a7
to
43b3fab
Compare
57ce255
to
1a2f7de
Compare
Awesome! Thank you both for all your efforts. |
Since upgrading to nan 2.8.0, my module which uses |
For starters, you seem to be missing The most likely reason for crashes is the missing |
The only change that comes to mind that affects diff --git a/nan.h b/nan.h
index 7c7699f..f1e5338 100644
--- a/nan.h
+++ b/nan.h
@@ -1626,6 +1626,11 @@ class Callback {
virtual ~AsyncBareProgressWorkerBase() {
}
+ void WorkComplete() {
+ WorkProgress();
+ AsyncWorker::WorkComplete();
+ }
+
virtual void WorkProgress() = 0;
virtual void Destroy() {
@@ -1807,11 +1812,6 @@ class AsyncProgressQueueWorker : public AsyncBareProgressQueueWorker<T> {
uv_mutex_destroy(&async_lock);
}
- void WorkComplete() {
- WorkProgress();
- AsyncWorker::WorkComplete();
- }
-
void WorkProgress() {
uv_mutex_lock(&async_lock);
If that fixes things, don't rely on it -- I can't promise we'll merge it (although it would be nice to hear if it does) Regardless, please fix your missing |
Hmm, adding |
I am guessing that my problem is related to the fact that I construct an instance of a |
I noticed that the |
The post-close delete in 1672 I believe... |
For the asynchronous case it almost surely needs to be allocated elsewhere than the stack, otherwise it would likely not survive until the worker thread is done. As for using it synchronously, nothing comes straight to mind, but I have never thought of doing that. |
Everything is a :-P |
If you don't queue the worker using |
Ok, I think I understand what happens... Your stack allocated worker goes out of scope. This triggers an async close. The async close handler, that runs later, accesses something that is already gone... boom. |
Ok, I don't get it ... @mkrufky is more qualified to spot this anyway ... |
Ok, I'm just going to avoid using my It might be nice if the |
I think, change LoadWorker *worker = new LoadWorker(...) and obviously, fix all the worker->Destroy(); at the end of the function. (and never |
@mkrufky Ha! That's a much quicker fix - great suggestion! |
And the |
|
Ah, thanks for the follow-up @kkoopa. I actually don't call |
In general, is this an ok way of creating synchronous variants of async methods? In other words, is creating It is convenient for me, but I don't mind extracting the logic into a separate object if need be. That would just be a little more boilerplate. |
Please, no. This is the first time I have ever seen this. The |
Yeah, in this case it's a little more complicated because there is a bunch of intermediate state that both |
This was an invalid use of the nan API which became more apparent w/ nan 2.8
Maybe |
It is a virtual method serving part of the role of a destructor, which are public. I am not sure if it could be made protected, but that would constitute a breaking change so it will not happen in the foreseeable future. Again, legacy. |
I think we can safely do it without breaking anything, unless there's other code out there that calls these functions diff --git a/nan.h b/nan.h
index 7c7699f..a0b82ee 100644
--- a/nan.h
+++ b/nan.h
@@ -1502,6 +1502,7 @@ class Callback {
};
/* abstract */ class AsyncWorker {
+ friend class AsyncQueueWorker;
public:
explicit AsyncWorker(Callback *callback_)
: callback(callback_), errmsg_(NULL) {
@@ -1571,14 +1572,14 @@ class Callback {
uv_work_t request;
- virtual void Destroy() {
- delete this;
- }
-
protected:
Persistent<v8::Object> persistentHandle;
Callback *callback;
+ virtual void Destroy() {
+ delete this;
+ }
+
virtual void HandleOKCallback() {
HandleScope scope;
@@ -1628,6 +1629,7 @@ class Callback {
virtual void WorkProgress() = 0;
+ protected:
virtual void Destroy() {
uv_close(reinterpret_cast<uv_handle_t*>(&async), AsyncClose_);
}
@@ -1856,25 +1858,31 @@ class AsyncProgressQueueWorker : public AsyncBareProgressQueueWorker<T> {
std::queue<std::pair<T*, size_t> > asyncdata_;
};
-inline void AsyncExecute (uv_work_t* req) {
- AsyncWorker *worker = static_cast<AsyncWorker*>(req->data);
- worker->Execute();
-}
+class AsyncQueueWorker {
+ public:
+ explicit AsyncQueueWorker(AsyncWorker* worker) {
+ uv_queue_work(
+ uv_default_loop()
+ , &worker->request
+ , AsyncExecute
+ , reinterpret_cast<uv_after_work_cb>(AsyncExecuteComplete)
+ );
+ }
-inline void AsyncExecuteComplete (uv_work_t* req) {
- AsyncWorker* worker = static_cast<AsyncWorker*>(req->data);
- worker->WorkComplete();
- worker->Destroy();
-}
+ private:
+ NAN_DISALLOW_ASSIGN_COPY_MOVE(AsyncQueueWorker)
-inline void AsyncQueueWorker (AsyncWorker* worker) {
- uv_queue_work(
- uv_default_loop()
- , &worker->request
- , AsyncExecute
- , reinterpret_cast<uv_after_work_cb>(AsyncExecuteComplete)
- );
-}
+ inline static void AsyncExecute (uv_work_t* req) {
+ AsyncWorker *worker = static_cast<AsyncWorker*>(req->data);
+ worker->Execute();
+ }
+
+ inline static void AsyncExecuteComplete (uv_work_t* req) {
+ AsyncWorker* worker = static_cast<AsyncWorker*>(req->data);
+ worker->WorkComplete();
+ worker->Destroy();
+ }
+};
namespace imp {
...passes all the tests |
add
AsyncProgressQueueWorker
: wake the main thread like AsyncProgressWorkerBase, except all events are queued and deliveredThere are often cases where we want to handle events initiated by threads other than node threads. Developers are often told to look at
AsyncProgressWorker
, which allows us to send progress updates from external threads, but this is not necessarily enough.AsyncProgressWorker
does what it needs to do, but if it is invoked many times before it wakes the main thread, only the last event will be delivered. This is great for sending "progress" style events, but sometimes we need to make sure that all events get delivered.AsyncProgressWorker
doesn't do the job in those cases.This PR refactors
AsyncProgressWorkerBase
to separate common elements and adds a new structure,AsyncProgressQueueWorker
that behaves likeAsyncProgressWorkerBase
except all events are queued and delivered.The first patch,
AsyncProgressWorkerBase inherits AsyncBareProgressWorker
is a no-op change, that simply separatesAsyncBareProgressWorker
fromAsyncProgressWorkerBase
.The second patch adds the new functionality:
AsyncProgressQueueWorker
The third patch adds a test that shows how
AsyncProgressWorker
can (and will) miss some events.The fourth patch adds tests that show how
AsyncProgressQueueWorker
does not miss events.The fifth patch adds adds documentation for the new
AsyncProgressQueueWorker
class.The sixth patch ensures that any possible remaining events in the queue are drained within
WorkComplete()
The API of
AsyncProgressQueueWorker
is exactly the same as the API ofAsyncProgressWorkerBase
-- it's a drop-in replacement. The only difference is that events will not be missed as a result of multiple wake requests being made before theuv_async_t
wakes the main thread. For more info, see the warning at the bottom of the uv_async_t documentation: