Skip to content

Commit 2f0894b

Browse files
Refactor delayed promise resolving.
1 parent e808574 commit 2f0894b

File tree

15 files changed

+204
-106
lines changed

15 files changed

+204
-106
lines changed

.travis.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ dist: bionic
44

55
jobs:
66
include:
7+
78
## TEST STAGE
89

9-
# Test main OSes on Node 10.x branch.
1010
- os: linux
1111
node_js: "10.16"
1212
env: ZMQ_DRAFT=true INCLUDE_COMPAT_TESTS=true
@@ -16,6 +16,7 @@ jobs:
1616
sudo: required
1717

1818
- os: osx
19+
osx_image: xcode10
1920
env: ZMQ_DRAFT=true
2021
node_js: "10.16"
2122

@@ -36,6 +37,7 @@ jobs:
3637
addons: {apt: {packages: libzmq3-dev}}
3738

3839
- os: osx
40+
osx_image: xcode10
3941
node_js: "10.16"
4042
env: ZMQ_SHARED=true
4143
addons: {homebrew: {packages: zeromq, update: true}}
@@ -56,6 +58,14 @@ jobs:
5658
# Skip GC tests due to https://github.com/node-ffi-napi/weak-napi/issues/16
5759
env: ZMQ_DRAFT=true SKIP_GC_TESTS=true
5860

61+
## ADDITIONAL TESTS
62+
63+
# This test ensures the delayed resolution of read/write promises is correct
64+
# by disabling immediate resolution (which happens 99% of the time) entirely.
65+
- os: linux
66+
node_js: "10.16"
67+
env: ZMQ_NO_SYNC_RESOLVE=true ZMQ_DRAFT=true INCLUDE_COMPAT_TESTS=true NODE_NO_WARNINGS=1
68+
5969
## PREBUILD STAGE
6070

6171
- stage: prebuild
@@ -86,6 +96,7 @@ jobs:
8696

8797
- stage: prebuild
8898
os: osx
99+
osx_image: xcode10
89100
node_js: "10.16"
90101
script: script/ci/prebuild.sh
91102

CHANGELOG.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
### Unreleased
1+
### v6.0.0-beta.4
22

3-
* Break out of busy loops automatically when the number of synchronous I/O operations moves beyond a built-in threshold. This avoids the ZeroMQ background I/O process(es) starving the Node.js event loop when it can process messages faster than the application, potentially causing decreased responsiveness and/or high memory usage.
3+
* Break out of busy loops automatically when the number of synchronous I/O operations moves beyond a built-in threshold. This avoids the ZeroMQ background I/O process(es) starving the Node.js event loop when it can process messages faster than the application. This could have caused decreased responsiveness and/or high memory usage. This only happens when sending/receiving messages as quickly as possible, such as in a benchmark or in test code.
4+
5+
* Fixed a memory leak in socket construction that would manifest itself when repeatedly creating many sockets.
46

57
### v6.0.0-beta.3
68

binding.gyp

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
'variables': {
33
'zmq_shared%': 'false',
44
'zmq_draft%': 'false',
5+
'zmq_no_sync_resolve%': 'false',
56
},
67

78
'targets': [
@@ -52,6 +53,12 @@
5253
],
5354
}],
5455

56+
["zmq_no_sync_resolve == 'true'", {
57+
'defines': [
58+
'ZMQ_NO_SYNC_RESOLVE',
59+
],
60+
}],
61+
5562
["zmq_shared == 'true'", {
5663
'link_settings': {
5764
'libraries': ['-lzmq'],
@@ -85,7 +92,7 @@
8592
'-std=gnu++1y'
8693
],
8794
'cflags_cc+': [
88-
'-std=c++14',
95+
'-std=c++17',
8996
'-Wno-missing-field-initializers',
9097
],
9198
}],
@@ -94,7 +101,7 @@
94101
'xcode_settings': {
95102
# https://pewpewthespells.com/blog/buildsettings.html
96103
'CLANG_CXX_LIBRARY': 'libc++',
97-
'CLANG_CXX_LANGUAGE_STANDARD': 'c++14',
104+
'CLANG_CXX_LANGUAGE_STANDARD': 'c++17',
98105
'MACOSX_DEPLOYMENT_TARGET': '10.9',
99106
'WARNING_CFLAGS': [
100107
'-Wextra',
@@ -112,6 +119,9 @@
112119
# 2 - MultiThreadedDLL (/MD)
113120
# 3 - MultiThreadedDebugDLL (/MDd)
114121
'RuntimeLibrary': 3,
122+
'AdditionalOptions': [
123+
'-std:c++17',
124+
],
115125
},
116126
},
117127
}],
@@ -126,7 +136,7 @@
126136
'-std=gnu++1y'
127137
],
128138
'cflags_cc+': [
129-
'-std=c++14',
139+
'-std=c++17',
130140
'-flto',
131141
'-Wno-missing-field-initializers',
132142
],
@@ -136,7 +146,7 @@
136146
# https://pewpewthespells.com/blog/buildsettings.html
137147
'xcode_settings': {
138148
'CLANG_CXX_LIBRARY': 'libc++',
139-
'CLANG_CXX_LANGUAGE_STANDARD': 'c++14',
149+
'CLANG_CXX_LANGUAGE_STANDARD': 'c++17',
140150
'MACOSX_DEPLOYMENT_TARGET': '10.9',
141151
'LLVM_LTO': 'YES',
142152
'GCC_OPTIMIZATION_LEVEL': '3',
@@ -154,6 +164,9 @@
154164
# 2 - MultiThreadedDLL (/MD)
155165
# 3 - MultiThreadedDebugDLL (/MDd)
156166
'RuntimeLibrary': 2,
167+
'AdditionalOptions': [
168+
'-std:c++17',
169+
],
157170
},
158171
'VCLinkerTool': {
159172
'AdditionalOptions': ['/ignore:4099'],

script/build.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ else
5757
echo > "${SRC_DIR}/builds/cmake/Modules/ClangFormat.cmake"
5858
fi
5959

60-
cmake -G "${CMAKE_GENERATOR}" "${BUILD_OPTIONS}" -DCMAKE_INSTALL_PREFIX="${PATH_PREFIX}" -DCMAKE_INSTALL_LIBDIR=lib -DBUILD_STATIC=ON -DBUILD_TESTS=OFF -DBUILD_SHARED=OFF "${SRC_DIR}"
60+
cmake -G "${CMAKE_GENERATOR}" "${BUILD_OPTIONS}" -DCMAKE_INSTALL_PREFIX="${PATH_PREFIX}" -DCMAKE_INSTALL_LIBDIR=lib -DBUILD_STATIC=ON -DBUILD_TESTS=OFF -DBUILD_SHARED=OFF -DWITH_DOCS=OFF "${SRC_DIR}"
6161

6262
if [ -n "${WINDIR}" ]; then
6363
cmake --build . --config Release --target install -- -verbosity:Minimal -maxcpucount

script/ci/install.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ if [ -n "${ZMQ_DRAFT}" ]; then
2525
export npm_config_zmq_draft=true
2626
fi
2727

28+
if [ -n "${ZMQ_NO_SYNC_RESOLVE}" ]; then
29+
export npm_config_zmq_no_sync_resolve=true
30+
fi
31+
2832
export npm_config_build_from_source=true
2933

3034
# Installing node-gyp globally facilitates calling it in various ways, not just

src/index.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1662,25 +1662,27 @@ function defineOpt<T extends {prototype: any}, K extends ReadableKeys<PrototypeO
16621662
const desc: PropertyDescriptor = {}
16631663

16641664
if (acc & Acc.Read) {
1665+
const getter = `get${type}Option`
16651666
if (values) {
16661667
desc.get = function get(this: any) {
1667-
return values[this[`get${type}Option`](id)]
1668+
return values[this[getter](id)]
16681669
}
16691670
} else {
16701671
desc.get = function get(this: any) {
1671-
return this[`get${type}Option`](id)
1672+
return this[getter](id)
16721673
}
16731674
}
16741675
}
16751676

16761677
if (acc & Acc.Write) {
1678+
const setter = `set${type}Option`
16771679
if (values) {
16781680
desc.set = function set(this: any, val: any) {
1679-
this[`set${type}Option`](id, values.indexOf(val))
1681+
this[setter](id, values.indexOf(val))
16801682
}
16811683
} else {
16821684
desc.set = function set(this: any, val: any) {
1683-
this[`set${type}Option`](id, val)
1685+
this[setter](id, val)
16841686
}
16851687
}
16861688
}

src/observer.cc

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include "incoming_msg.h"
88
#include "util/async_scope.h"
9+
#include "util/take.h"
910

1011
#include <array>
1112

@@ -315,21 +316,18 @@ Napi::Value Observer::Receive(const Napi::CallbackInfo& info) {
315316
if (!ValidateArguments(info, {})) return Env().Undefined();
316317
if (!ValidateOpen()) return Env().Undefined();
317318

319+
if (poller.Reading()) {
320+
ErrnoException(Env(), EAGAIN).ThrowAsJavaScriptException();
321+
return Env().Undefined();
322+
}
323+
318324
if (HasEvents()) {
319-
/* We can read from the socket immediately. This is a separate code
320-
path so we can avoid creating a lambda. */
325+
/* We can read from the socket immediately. This is a fast path. */
321326
auto res = Napi::Promise::Deferred::New(Env());
322327
Receive(res);
323328
return res.Promise();
324329
} else {
325-
/* Check if we are already polling for reads. Only one promise may
326-
receive the next message, so we must ensure that receive
327-
operations are in sequence. */
328-
if (poller.PollingReadable()) {
329-
ErrnoException(Env(), EAGAIN).ThrowAsJavaScriptException();
330-
return Env().Undefined();
331-
}
332-
330+
poller.PollReadable(0);
333331
return poller.ReadPromise();
334332
}
335333
}
@@ -351,13 +349,16 @@ void Observer::Initialize(Module& module, Napi::Object& exports) {
351349
}
352350

353351
void Observer::Poller::ReadableCallback() {
354-
AsyncScope scope(read_deferred.Env(), socket.async_context);
355-
socket.Receive(read_deferred);
352+
assert(read_deferred);
353+
354+
AsyncScope scope(socket.Env(), socket.async_context);
355+
socket.Receive(take(read_deferred));
356356
}
357357

358358
Napi::Value Observer::Poller::ReadPromise() {
359-
read_deferred = Napi::Promise::Deferred(read_deferred.Env());
360-
zmq::Poller<Poller>::PollReadable(0);
361-
return read_deferred.Promise();
359+
assert(!read_deferred);
360+
361+
read_deferred = Napi::Promise::Deferred(socket.Env());
362+
return read_deferred->Promise();
362363
}
363364
}

src/observer.h

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
#include "poller.h"
77

8+
#include <optional>
9+
810
namespace zmq {
911
class Module;
1012

@@ -31,14 +33,17 @@ class Observer : public Napi::ObjectWrap<Observer>, public Closable {
3133

3234
class Poller : public zmq::Poller<Poller> {
3335
Observer& socket;
34-
Napi::Promise::Deferred read_deferred;
36+
std::optional<Napi::Promise::Deferred> read_deferred;
3537

3638
public:
37-
explicit Poller(Observer& observer)
38-
: socket(observer), read_deferred(socket.Env()) {}
39+
explicit Poller(Observer& observer) : socket(observer) {}
3940

4041
Napi::Value ReadPromise();
4142

43+
inline bool Reading() const {
44+
return read_deferred.has_value();
45+
}
46+
4247
inline bool ValidateReadable() const {
4348
return socket.HasEvents();
4449
}

src/poller.h

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -45,29 +45,21 @@ class Poller {
4545
/* Safely close and release all handles. This can be called before
4646
destruction to release resources early. */
4747
inline void Close() {
48-
/* Trigger all watched events manually, which causes any pending
49-
operation to succeed or fail immediately. */
50-
if (events) Trigger(events);
48+
/* Trigger watched events manually, which causes any pending operation
49+
to succeed or fail immediately. */
50+
Trigger(events);
5151

5252
/* Pollers and timers are stopped automatically by uv_close() which is
5353
wrapped in UvHandle. */
5454

5555
/* Release references to all UV handles. */
56-
poll.reset(nullptr);
57-
readable_timer.reset(nullptr);
58-
writable_timer.reset(nullptr);
56+
poll.reset();
57+
readable_timer.reset();
58+
writable_timer.reset();
5959

6060
if (finalize) finalize();
6161
}
6262

63-
inline bool PollingReadable() const {
64-
return events & UV_READABLE;
65-
}
66-
67-
inline bool PollingWritable() const {
68-
return events & UV_WRITABLE;
69-
}
70-
7163
/* Start polling for readable state, with the given timeout. */
7264
inline void PollReadable(int64_t timeout) {
7365
assert((events & UV_READABLE) == 0);
@@ -119,13 +111,19 @@ class Poller {
119111

120112
/* Trigger any events that are ready. Use validation callbacks to see
121113
which events are actually available. */
122-
inline void Trigger() {
114+
inline void TriggerReadable() {
123115
if (events & UV_READABLE) {
124-
if (static_cast<T*>(this)->ValidateReadable()) Trigger(UV_READABLE);
116+
if (static_cast<T*>(this)->ValidateReadable()) {
117+
Trigger(UV_READABLE);
118+
}
125119
}
120+
}
126121

122+
inline void TriggerWritable() {
127123
if (events & UV_WRITABLE) {
128-
if (static_cast<T*>(this)->ValidateWritable()) Trigger(UV_WRITABLE);
124+
if (static_cast<T*>(this)->ValidateWritable()) {
125+
Trigger(UV_WRITABLE);
126+
}
129127
}
130128
}
131129

@@ -159,8 +157,9 @@ class Poller {
159157
static void Callback(uv_poll_t* poll, int32_t status, int32_t events) {
160158
if (status == 0) {
161159
auto& poller = *reinterpret_cast<Poller*>(poll->data);
162-
poller.Trigger();
160+
poller.TriggerReadable();
161+
poller.TriggerWritable();
163162
}
164-
};
163+
}
165164
};
166165
}

0 commit comments

Comments
 (0)