@homaiohq/napi-quickfix 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CMakeLists.txt +135 -0
- package/LICENSE +21 -0
- package/README.md +638 -2
- package/THIRD_PARTY_LICENSES +48 -0
- package/cpp/acceptor_wrap.cpp +468 -0
- package/cpp/acceptor_wrap.h +116 -0
- package/cpp/addon.cpp +87 -0
- package/cpp/application_bridge.cpp +466 -0
- package/cpp/application_bridge.h +204 -0
- package/cpp/data_dictionary_wrap.cpp +165 -0
- package/cpp/data_dictionary_wrap.h +59 -0
- package/cpp/engine_common.cpp +5 -0
- package/cpp/engine_common.h +127 -0
- package/cpp/engine_workers.h +167 -0
- package/cpp/errors.h +142 -0
- package/cpp/field_map_ops.h +492 -0
- package/cpp/group_wrap.cpp +221 -0
- package/cpp/group_wrap.h +76 -0
- package/cpp/initiator_wrap.cpp +492 -0
- package/cpp/initiator_wrap.h +104 -0
- package/cpp/message_wrap.cpp +288 -0
- package/cpp/message_wrap.h +95 -0
- package/cpp/session_id_wrap.cpp +91 -0
- package/cpp/session_id_wrap.h +46 -0
- package/cpp/session_op_gate.h +201 -0
- package/cpp/session_settings_wrap.cpp +98 -0
- package/cpp/session_settings_wrap.h +40 -0
- package/cpp/session_static.cpp +220 -0
- package/cpp/session_wrap.cpp +438 -0
- package/cpp/session_wrap.h +144 -0
- package/dist/cjs/acceptor.d.ts +29 -0
- package/dist/cjs/acceptor.js +38 -0
- package/dist/cjs/application.d.ts +115 -0
- package/dist/cjs/application.js +62 -0
- package/dist/cjs/data-dictionary.d.ts +67 -0
- package/dist/cjs/data-dictionary.js +105 -0
- package/dist/cjs/engine.d.ts +152 -0
- package/dist/cjs/engine.js +183 -0
- package/dist/cjs/enums.d.ts +56 -0
- package/dist/cjs/enums.js +44 -0
- package/dist/cjs/field-value.d.ts +12 -0
- package/dist/cjs/field-value.js +40 -0
- package/dist/cjs/generated/fields.d.ts +6122 -0
- package/dist/cjs/generated/fields.js +6127 -0
- package/dist/cjs/generated/values.d.ts +8548 -0
- package/dist/cjs/generated/values.js +8580 -0
- package/dist/cjs/group.d.ts +126 -0
- package/dist/cjs/group.js +185 -0
- package/dist/cjs/index.d.ts +68 -0
- package/dist/cjs/index.js +73 -0
- package/dist/cjs/initiator.d.ts +29 -0
- package/dist/cjs/initiator.js +38 -0
- package/dist/cjs/load-native.cjs +5 -0
- package/dist/cjs/message.d.ts +238 -0
- package/dist/cjs/message.js +299 -0
- package/dist/cjs/native.d.ts +325 -0
- package/dist/cjs/native.js +22 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/session-id.d.ts +51 -0
- package/dist/cjs/session-id.js +87 -0
- package/dist/cjs/session-settings.d.ts +46 -0
- package/dist/cjs/session-settings.js +75 -0
- package/dist/cjs/session.d.ts +213 -0
- package/dist/cjs/session.js +337 -0
- package/dist/esm/acceptor.d.ts +29 -0
- package/dist/esm/acceptor.js +34 -0
- package/dist/esm/application.d.ts +115 -0
- package/dist/esm/application.js +57 -0
- package/dist/esm/data-dictionary.d.ts +67 -0
- package/dist/esm/data-dictionary.js +101 -0
- package/dist/esm/engine.d.ts +152 -0
- package/dist/esm/engine.js +179 -0
- package/dist/esm/enums.d.ts +56 -0
- package/dist/esm/enums.js +35 -0
- package/dist/esm/field-value.d.ts +12 -0
- package/dist/esm/field-value.js +37 -0
- package/dist/esm/generated/fields.d.ts +6122 -0
- package/dist/esm/generated/fields.js +6124 -0
- package/dist/esm/generated/values.d.ts +8548 -0
- package/dist/esm/generated/values.js +8564 -0
- package/dist/esm/group.d.ts +126 -0
- package/dist/esm/group.js +181 -0
- package/dist/esm/index.d.ts +68 -0
- package/dist/esm/index.js +43 -0
- package/dist/esm/initiator.d.ts +29 -0
- package/dist/esm/initiator.js +34 -0
- package/dist/esm/load-native.cjs +5 -0
- package/dist/esm/message.d.ts +238 -0
- package/dist/esm/message.js +293 -0
- package/dist/esm/native.d.ts +325 -0
- package/dist/esm/native.js +16 -0
- package/dist/esm/session-id.d.ts +51 -0
- package/dist/esm/session-id.js +82 -0
- package/dist/esm/session-settings.d.ts +46 -0
- package/dist/esm/session-settings.js +71 -0
- package/dist/esm/session.d.ts +213 -0
- package/dist/esm/session.js +329 -0
- package/package.json +95 -4
- package/prebuilds/darwin-arm64/node.napi.node +0 -0
- package/prebuilds/darwin-x64/node.napi.node +0 -0
- package/prebuilds/linux-x64/node.napi.glibc.node +0 -0
- package/prebuilds/linux-x64/node.napi.musl.node +0 -0
- package/prebuilds/win32-x64/node.napi.node +0 -0
- package/src/load-native.cjs +5 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
THIRD-PARTY LICENSES
|
|
2
|
+
====================
|
|
3
|
+
|
|
4
|
+
This file lists the licenses of third-party software that is bundled with, or
|
|
5
|
+
statically linked into, the distributed artifacts of @homaiohq/napi-quickfix.
|
|
6
|
+
|
|
7
|
+
The npm package ships prebuilt native binaries (`prebuilds/`) that statically
|
|
8
|
+
link the QuickFIX C++ FIX-protocol engine. QuickFIX itself is NOT vendored in
|
|
9
|
+
this source repository — it is fetched at build time from its upstream Git tag
|
|
10
|
+
via CMake FetchContent — but because its compiled code is embedded in the
|
|
11
|
+
published binaries, its license and the acknowledgement below are reproduced
|
|
12
|
+
here as required by that license.
|
|
13
|
+
|
|
14
|
+
--------------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
QuickFIX (https://github.com/quickfix/quickfix)
|
|
17
|
+
-----------------------------------------------
|
|
18
|
+
|
|
19
|
+
Version linked: v1.16.0
|
|
20
|
+
License: QuickFIX Software License, Version 1.0
|
|
21
|
+
License source: https://raw.githubusercontent.com/quickfix/quickfix/v1.16.0/LICENSE
|
|
22
|
+
|
|
23
|
+
This product includes software developed by quickfixengine.org
|
|
24
|
+
(http://www.quickfixengine.org/).
|
|
25
|
+
|
|
26
|
+
The full, verbatim text of the QuickFIX Software License, Version 1.0 follows:
|
|
27
|
+
|
|
28
|
+
--------------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
# QuickFIX Software License, Version 1.0
|
|
31
|
+
|
|
32
|
+
The complete text of this license is as follows:
|
|
33
|
+
|
|
34
|
+
Copyright (c) 2001-2020 Oren Miller
|
|
35
|
+
|
|
36
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
37
|
+
|
|
38
|
+
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
39
|
+
|
|
40
|
+
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
41
|
+
|
|
42
|
+
3. The end-user documentation included with the redistribution, if any, must include the following acknowledgment: "This product includes software developed by quickfixengine.org (http://www.quickfixengine.org/)." Alternately, this acknowledgment may appear in the software itself, if and wherever such third-party acknowledgments normally appear.
|
|
43
|
+
|
|
44
|
+
4. The names "QuickFIX" and "quickfixengine.org" must not be used to endorse or promote products derived from this software without prior written permission. For written permission, please contact ask@quickfixengine.org
|
|
45
|
+
|
|
46
|
+
5. Products derived from this software may not be called "QuickFIX", nor may "QuickFIX" appear in their name, without prior written permission of quickfixengine.org
|
|
47
|
+
|
|
48
|
+
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL QUICKFIXENGINE.ORG OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
#include "acceptor_wrap.h"
|
|
2
|
+
|
|
3
|
+
#include <memory>
|
|
4
|
+
#include <string>
|
|
5
|
+
|
|
6
|
+
#include "quickfix/FileLog.h"
|
|
7
|
+
#include "quickfix/FileStore.h"
|
|
8
|
+
|
|
9
|
+
#include "engine_common.h"
|
|
10
|
+
#include "engine_workers.h"
|
|
11
|
+
#include "errors.h"
|
|
12
|
+
#include "session_id_wrap.h"
|
|
13
|
+
#include "session_op_gate.h"
|
|
14
|
+
#include "session_settings_wrap.h"
|
|
15
|
+
#include "session_wrap.h"
|
|
16
|
+
|
|
17
|
+
namespace napi_quickfix {
|
|
18
|
+
|
|
19
|
+
Napi::FunctionReference AcceptorWrap::constructor_;
|
|
20
|
+
|
|
21
|
+
namespace {
|
|
22
|
+
|
|
23
|
+
std::string OptString(Napi::Object opts, const char* key,
|
|
24
|
+
const std::string& dflt) {
|
|
25
|
+
if (opts.Has(key)) {
|
|
26
|
+
Napi::Value v = opts.Get(key);
|
|
27
|
+
if (v.IsString()) return v.As<Napi::String>().Utf8Value();
|
|
28
|
+
}
|
|
29
|
+
return dflt;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
} // namespace
|
|
33
|
+
|
|
34
|
+
Napi::Object AcceptorWrap::Init(Napi::Env env, Napi::Object exports) {
|
|
35
|
+
Napi::Function func = DefineClass(
|
|
36
|
+
env, "Acceptor",
|
|
37
|
+
{
|
|
38
|
+
InstanceMethod("start", &AcceptorWrap::Start),
|
|
39
|
+
InstanceMethod("stop", &AcceptorWrap::Stop),
|
|
40
|
+
InstanceMethod("isLoggedOn", &AcceptorWrap::IsLoggedOn),
|
|
41
|
+
InstanceMethod("getSessions", &AcceptorWrap::GetSessions),
|
|
42
|
+
InstanceMethod("getSession", &AcceptorWrap::GetSession),
|
|
43
|
+
InstanceMethod("ref", &AcceptorWrap::Ref),
|
|
44
|
+
InstanceMethod("unref", &AcceptorWrap::Unref),
|
|
45
|
+
});
|
|
46
|
+
constructor_ = Napi::Persistent(func);
|
|
47
|
+
constructor_.SuppressDestruct();
|
|
48
|
+
exports.Set("AcceptorWrap", func);
|
|
49
|
+
return exports;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
AcceptorWrap::AcceptorWrap(const Napi::CallbackInfo& info)
|
|
53
|
+
: Napi::ObjectWrap<AcceptorWrap>(info) {
|
|
54
|
+
Napi::Env env = info.Env();
|
|
55
|
+
if (info.Length() < 1 || !info[0].IsObject()) {
|
|
56
|
+
throw Napi::TypeError::New(
|
|
57
|
+
env, "new Acceptor({ settings, handlers?, store?, log? })");
|
|
58
|
+
}
|
|
59
|
+
Napi::Object opts = info[0].As<Napi::Object>();
|
|
60
|
+
|
|
61
|
+
SessionSettingsWrap* settingsWrap =
|
|
62
|
+
SessionSettingsWrap::UnwrapArg(env, opts.Get("settings"), "settings");
|
|
63
|
+
|
|
64
|
+
Napi::Object handlers;
|
|
65
|
+
if (opts.Has("handlers") && opts.Get("handlers").IsObject()) {
|
|
66
|
+
handlers = opts.Get("handlers").As<Napi::Object>();
|
|
67
|
+
} else {
|
|
68
|
+
handlers = Napi::Object::New(env);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
std::string store = OptString(opts, "store", "file");
|
|
72
|
+
std::string log = OptString(opts, "log", "screen");
|
|
73
|
+
|
|
74
|
+
const FIX::SessionSettings& settings = settingsWrap->Settings();
|
|
75
|
+
|
|
76
|
+
bridge_ = std::make_unique<ApplicationBridge>(env, handlers);
|
|
77
|
+
|
|
78
|
+
NQ_TRY(env) {
|
|
79
|
+
if (store == "memory") {
|
|
80
|
+
storeFactory_ = std::make_unique<FIX::MemoryStoreFactory>();
|
|
81
|
+
} else {
|
|
82
|
+
storeFactory_ = std::make_unique<FIX::FileStoreFactory>(settings);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
std::unique_ptr<FIX::LogFactory> logFactory;
|
|
86
|
+
if (log == "none") {
|
|
87
|
+
logFactory = std::make_unique<NullLogFactory>();
|
|
88
|
+
} else if (log == "file") {
|
|
89
|
+
logFactory = std::make_unique<FIX::FileLogFactory>(settings);
|
|
90
|
+
} else {
|
|
91
|
+
logFactory = std::make_unique<FIX::ScreenLogFactory>(settings);
|
|
92
|
+
}
|
|
93
|
+
logFactory_ = std::make_unique<TrackingLogFactory>(std::move(logFactory));
|
|
94
|
+
|
|
95
|
+
RejectDuplicateSessions(settings, "acceptor");
|
|
96
|
+
try {
|
|
97
|
+
acceptor_ = std::make_unique<SocketAcceptor>(
|
|
98
|
+
*bridge_, *storeFactory_, settings, *logFactory_);
|
|
99
|
+
} catch (...) {
|
|
100
|
+
// A [SESSION] after the first valid one threw: QuickFIX has leaked the
|
|
101
|
+
// sessions it had already created (see DestroyOrphanedSessions) and the
|
|
102
|
+
// engine-level Log it requested before creating any of them (see
|
|
103
|
+
// TrackingLogFactory). Both would otherwise only have been reclaimed by
|
|
104
|
+
// the engine destructor that never runs.
|
|
105
|
+
DestroyOrphanedSessions(settings, "acceptor");
|
|
106
|
+
logFactory_->DestroyOrphanedEngineLog();
|
|
107
|
+
throw;
|
|
108
|
+
}
|
|
109
|
+
sessionIDs_ = acceptor_->getSessions();
|
|
110
|
+
SessionOpGate::Register(sessionIDs_, gate_);
|
|
111
|
+
} NQ_CATCH(env)
|
|
112
|
+
|
|
113
|
+
// Deterministic teardown on env shutdown, before the TSFN is finalized. See
|
|
114
|
+
// InitiatorWrap for the rationale.
|
|
115
|
+
env_ = env;
|
|
116
|
+
cleanupHook_ = env.AddCleanupHook(&AcceptorWrap::CleanupEntry, this);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
void AcceptorWrap::CleanupEntry(AcceptorWrap* self) {
|
|
120
|
+
self->cleanupHookFired_ = true;
|
|
121
|
+
self->Teardown(true);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
AcceptorWrap::~AcceptorWrap() {
|
|
125
|
+
if (!cleanupHookFired_ && !cleanupHook_.IsEmpty() && env_ != nullptr) {
|
|
126
|
+
cleanupHook_.Remove(env_);
|
|
127
|
+
}
|
|
128
|
+
Teardown(true);
|
|
129
|
+
DestroyEngine();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
void AcceptorWrap::Teardown(bool force) {
|
|
133
|
+
// Runs on the MAIN thread. Ordering (see InitiatorWrap::Teardown for detail):
|
|
134
|
+
// 1. Deactivate the bridge so callbacks during stop() pass through without
|
|
135
|
+
// touching the TSFN or waiting on the frozen JS loop (breaks deadlock).
|
|
136
|
+
// 2. Join the stop() worker if one is in flight, so the engine is never
|
|
137
|
+
// destroyed under its FIX::Acceptor::stop() / gate freeze. Hence the
|
|
138
|
+
// early return is keyed on tornDown_, not on stopped_.
|
|
139
|
+
// 3. stop(force): joins the QuickFIX network threads (a no-op if the
|
|
140
|
+
// worker's stop() completed). That join lasts until the network thread's
|
|
141
|
+
// next poll timeout, up to 1 s (see SocketAcceptor in the header).
|
|
142
|
+
// 4. Abort the TSFN: safe now that no producer thread remains.
|
|
143
|
+
if (tornDown_) return;
|
|
144
|
+
tornDown_ = true;
|
|
145
|
+
stopped_ = true;
|
|
146
|
+
if (bridge_) {
|
|
147
|
+
bridge_->Deactivate();
|
|
148
|
+
}
|
|
149
|
+
if (stopSync_) {
|
|
150
|
+
stopSync_->Wait();
|
|
151
|
+
stopSync_.reset();
|
|
152
|
+
}
|
|
153
|
+
if (acceptor_ && started_) {
|
|
154
|
+
try {
|
|
155
|
+
acceptor_->stop(force);
|
|
156
|
+
} catch (...) {
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (bridge_) {
|
|
160
|
+
bridge_->Abort();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Destroy the FIX engine (and with it every FIX::Session it owns) on the JS
|
|
165
|
+
// thread, with no session operation in flight on a libuv thread. Called from
|
|
166
|
+
// the destructor only; stop() uses DestroyStoppedEngine() below instead, as
|
|
167
|
+
// its worker has already Freeze()d the gate on the worker thread (see Stop()).
|
|
168
|
+
// The gate is per engine, so Freeze() here only waits for operations on THIS
|
|
169
|
+
// engine's sessions, and Teardown() has already deactivated its bridge and
|
|
170
|
+
// joined its network threads: none of those operations can be blocked on the
|
|
171
|
+
// JS thread (or on a session mutex), so Freeze() cannot deadlock. Operations
|
|
172
|
+
// on other engines -- which may well be blocked in a BlockingCall that needs
|
|
173
|
+
// this very thread -- are not waited for.
|
|
174
|
+
void AcceptorWrap::DestroyEngine() {
|
|
175
|
+
if (!acceptor_) return;
|
|
176
|
+
gate_->Freeze();
|
|
177
|
+
SessionOpGate::Unregister(sessionIDs_, gate_.get());
|
|
178
|
+
acceptor_.reset();
|
|
179
|
+
gate_->Unfreeze();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// The stop() path: the gate was frozen by the stop worker; destroy and lift
|
|
183
|
+
// the freeze. JS thread only.
|
|
184
|
+
void AcceptorWrap::DestroyStoppedEngine() {
|
|
185
|
+
SessionOpGate::Unregister(sessionIDs_, gate_.get());
|
|
186
|
+
acceptor_.reset();
|
|
187
|
+
gate_->Unfreeze();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
Napi::Value AcceptorWrap::Start(const Napi::CallbackInfo& info) {
|
|
191
|
+
Napi::Env env = info.Env();
|
|
192
|
+
auto deferred = Napi::Promise::Deferred::New(env);
|
|
193
|
+
if (stopped_) {
|
|
194
|
+
deferred.Reject(Napi::Error::New(env, "Acceptor has been stopped").Value());
|
|
195
|
+
return deferred.Promise();
|
|
196
|
+
}
|
|
197
|
+
if (started_) {
|
|
198
|
+
deferred.Resolve(env.Undefined());
|
|
199
|
+
return deferred.Promise();
|
|
200
|
+
}
|
|
201
|
+
if (busy_) {
|
|
202
|
+
deferred.Reject(
|
|
203
|
+
Napi::Error::New(env, "Acceptor start/stop already in progress")
|
|
204
|
+
.Value());
|
|
205
|
+
return deferred.Promise();
|
|
206
|
+
}
|
|
207
|
+
busy_ = true;
|
|
208
|
+
started_ = true; // optimistic; reset in onSettled on failure
|
|
209
|
+
|
|
210
|
+
FIX::SocketAcceptor* acceptor = acceptor_.get();
|
|
211
|
+
auto* self = this;
|
|
212
|
+
auto* worker = new EngineOpWorker(
|
|
213
|
+
env, info.This().As<Napi::Object>(),
|
|
214
|
+
[acceptor]() { acceptor->start(); },
|
|
215
|
+
[self](bool ok, const std::shared_ptr<EngineOpWorker::Settlement>& s) {
|
|
216
|
+
self->busy_ = false;
|
|
217
|
+
if (!ok) self->started_ = false;
|
|
218
|
+
s->Settle();
|
|
219
|
+
});
|
|
220
|
+
Napi::Promise promise = worker->Promise();
|
|
221
|
+
worker->Queue();
|
|
222
|
+
return promise;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
Napi::Value AcceptorWrap::Stop(const Napi::CallbackInfo& info) {
|
|
226
|
+
Napi::Env env = info.Env();
|
|
227
|
+
bool force = false;
|
|
228
|
+
if (info.Length() >= 1 && !info[0].IsUndefined()) {
|
|
229
|
+
force = info[0].ToBoolean().Value();
|
|
230
|
+
}
|
|
231
|
+
auto deferred = Napi::Promise::Deferred::New(env);
|
|
232
|
+
// A stop() is in flight (its worker is running, or a graceful stop is
|
|
233
|
+
// waiting for its queued 'logout' events to be dispatched before destroying
|
|
234
|
+
// the engine): join it. Resolving right away here -- `stopped_` is already
|
|
235
|
+
// set -- would break the README's promise that the sessions are gone once
|
|
236
|
+
// stop() settles. The first call's `force` applies to both.
|
|
237
|
+
if (!stopPromise_.IsEmpty()) {
|
|
238
|
+
return stopPromise_.Value();
|
|
239
|
+
}
|
|
240
|
+
if (stopped_) {
|
|
241
|
+
deferred.Resolve(env.Undefined());
|
|
242
|
+
return deferred.Promise();
|
|
243
|
+
}
|
|
244
|
+
if (busy_) {
|
|
245
|
+
deferred.Reject(
|
|
246
|
+
Napi::Error::New(env, "Acceptor start/stop already in progress")
|
|
247
|
+
.Value());
|
|
248
|
+
return deferred.Promise();
|
|
249
|
+
}
|
|
250
|
+
if (!acceptor_) {
|
|
251
|
+
// Already torn down: just release the TSFN and resolve.
|
|
252
|
+
stopped_ = true;
|
|
253
|
+
if (bridge_) bridge_->Release();
|
|
254
|
+
deferred.Resolve(env.Undefined());
|
|
255
|
+
return deferred.Promise();
|
|
256
|
+
}
|
|
257
|
+
busy_ = true;
|
|
258
|
+
stopped_ = true;
|
|
259
|
+
// Lets Teardown() wait for the worker's operation instead of destroying the
|
|
260
|
+
// engine under it; cleared in onSettled once the operation has returned.
|
|
261
|
+
auto stopDone = std::make_shared<StopCompletion>();
|
|
262
|
+
stopSync_ = stopDone;
|
|
263
|
+
|
|
264
|
+
// A never-started engine takes the same async path (minus the stop() call):
|
|
265
|
+
// its FIX::Session objects are registered from construction, so a session
|
|
266
|
+
// operation may already be in flight against them and the destruction below
|
|
267
|
+
// must wait for it just the same.
|
|
268
|
+
FIX::SocketAcceptor* acceptor = acceptor_.get();
|
|
269
|
+
ApplicationBridge* bridge = bridge_.get();
|
|
270
|
+
const bool started = started_;
|
|
271
|
+
std::shared_ptr<SessionOpGate> gate = gate_;
|
|
272
|
+
auto* self = this;
|
|
273
|
+
auto* worker = new EngineOpWorker(
|
|
274
|
+
env, info.This().As<Napi::Object>(),
|
|
275
|
+
// Runs on the worker thread. stop()'s onLogout/toAdmin callbacks fire on
|
|
276
|
+
// QuickFIX threads while the main loop is free to service the TSFN.
|
|
277
|
+
[acceptor, gate, started, force, stopDone]() {
|
|
278
|
+
// Signal completion on every exit of this lambda, success or throw,
|
|
279
|
+
// and only after the gate freeze below has returned as well.
|
|
280
|
+
struct SignalOnExit {
|
|
281
|
+
std::shared_ptr<StopCompletion> done;
|
|
282
|
+
~SignalOnExit() { done->Signal(); }
|
|
283
|
+
} signalOnExit{stopDone};
|
|
284
|
+
try {
|
|
285
|
+
if (started) acceptor->stop(force);
|
|
286
|
+
} catch (...) {
|
|
287
|
+
gate->Freeze();
|
|
288
|
+
throw;
|
|
289
|
+
}
|
|
290
|
+
// stop() has joined the network threads. Now block new session
|
|
291
|
+
// operations (SendToTargetWorker) on THIS engine's
|
|
292
|
+
// sessions and wait for the in-flight ones to finish: they hold raw
|
|
293
|
+
// FIX::Session pointers that the destruction below is about to
|
|
294
|
+
// invalidate. Waiting HERE, on the worker thread, keeps the JS loop
|
|
295
|
+
// free for any BlockingCall such an operation is still making (e.g.
|
|
296
|
+
// sendToTarget() -> toApp). Other engines' operations are unaffected.
|
|
297
|
+
gate->Freeze();
|
|
298
|
+
},
|
|
299
|
+
[self, bridge, env, force, gate](
|
|
300
|
+
bool ok, const std::shared_ptr<EngineOpWorker::Settlement>& s) {
|
|
301
|
+
self->busy_ = false;
|
|
302
|
+
self->stopSync_.reset(); // the worker's operation has returned
|
|
303
|
+
// Settles the promise and lets a later stop() start over (the
|
|
304
|
+
// in-flight stop is finished, successfully or not).
|
|
305
|
+
auto settle = [self, s]() {
|
|
306
|
+
self->stopPromise_.Reset();
|
|
307
|
+
s->Settle();
|
|
308
|
+
};
|
|
309
|
+
if (!ok) {
|
|
310
|
+
// stop() threw part-way: the network threads may still be running,
|
|
311
|
+
// so the engine is NOT safe to destroy. Keep it, let session
|
|
312
|
+
// operations through again and clear stopped_ so that a later
|
|
313
|
+
// stop() -- or the destructor's Teardown() -- retries. Mirrors
|
|
314
|
+
// Start() clearing started_ on failure. The bridge is released (or
|
|
315
|
+
// aborted) as on success, so a failed stop() does not keep the
|
|
316
|
+
// event loop alive: the TSFN holds a loop reference until it is
|
|
317
|
+
// released, and a script that catches the rejection and falls off
|
|
318
|
+
// the end would otherwise never exit. From here on the engine's
|
|
319
|
+
// callbacks pass through without reaching JS; the retried stop()
|
|
320
|
+
// then destroys the engine as soon as its worker settles, since
|
|
321
|
+
// RunAfterQueued() refuses a released bridge.
|
|
322
|
+
self->stopped_ = false;
|
|
323
|
+
self->gate_->Unfreeze();
|
|
324
|
+
if (bridge) {
|
|
325
|
+
if (force) {
|
|
326
|
+
bridge->Abort();
|
|
327
|
+
} else {
|
|
328
|
+
bridge->Release();
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
settle();
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
// stop() has completed: the network threads are joined and session
|
|
335
|
+
// operations are frozen. Destroy the engine on the JS thread rather
|
|
336
|
+
// than at GC: QuickFIX only deletes its FIX::Session objects -- and
|
|
337
|
+
// removes them from the process-wide registry that lookupSession() /
|
|
338
|
+
// sendToTarget() read -- in the engine destructor. Deferring that to GC
|
|
339
|
+
// would leave stale Session handles resolvable and make a new engine
|
|
340
|
+
// with the same SessionIDs fail with ConfigError "Duplicate Session"
|
|
341
|
+
// until the old wrap happened to be collected. A stopped engine cannot
|
|
342
|
+
// be restarted anyway (start() rejects once stopped_ is set).
|
|
343
|
+
if (force || bridge == nullptr) {
|
|
344
|
+
// Force: abort the TSFN, discarding any callback still queued (a
|
|
345
|
+
// 'logout' listener may therefore never run), and destroy right away.
|
|
346
|
+
if (bridge) bridge->Abort();
|
|
347
|
+
self->DestroyStoppedEngine();
|
|
348
|
+
settle();
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
// Graceful: the 'logout' events stop() fired are still queued in the
|
|
352
|
+
// bridge, and their listeners may read the session they name
|
|
353
|
+
// (getSession() / Session handles are documented to stay live until
|
|
354
|
+
// stop() settles). Nothing orders the TSFN's dispatch of those events
|
|
355
|
+
// before this AsyncWorker completion, so destroy the engine -- and
|
|
356
|
+
// settle the promise -- only once they have been dispatched: queue the
|
|
357
|
+
// destruction through the bridge's own FIFO behind them, then release
|
|
358
|
+
// the TSFN so it closes once drained. The owner reference held by the
|
|
359
|
+
// settlement keeps this wrap alive until then.
|
|
360
|
+
const bool queued =
|
|
361
|
+
bridge->RunAfterQueued(env, [self, s, settle, gate](Napi::Env e) {
|
|
362
|
+
if (e == nullptr) {
|
|
363
|
+
// Discarded: the environment is being torn down. The wrap's
|
|
364
|
+
// own teardown (cleanup hook, then destructor) destroys the
|
|
365
|
+
// engine; touch nothing but the settlement and the gate,
|
|
366
|
+
// which this closure owns (the gate by shared_ptr). In
|
|
367
|
+
// particular do not reach through `self`: what keeps the
|
|
368
|
+
// wrap alive until here is the TSFN's reference on the
|
|
369
|
+
// napi_env (the env -- and with it every ObjectWrap
|
|
370
|
+
// finalizer -- is deleted only after the TSFN has dropped
|
|
371
|
+
// its queue), a Node internal this code must not lean on.
|
|
372
|
+
// The promise reference needs no special care: the wrap
|
|
373
|
+
// destructor deletes it, and runs with the env valid.
|
|
374
|
+
//
|
|
375
|
+
// The freeze the stop worker took is still held: lift it,
|
|
376
|
+
// as DestroyStoppedEngine() would have. A SendToTargetWorker
|
|
377
|
+
// parked in OpScope since then is otherwise never woken, and
|
|
378
|
+
// the destructor's own DestroyEngine() -- which nests its
|
|
379
|
+
// freeze inside this one -- never brings the count to zero
|
|
380
|
+
// either; the libuv pool thread it sits on would be parked
|
|
381
|
+
// for good, and whoever drains the pool at exit (a Worker's
|
|
382
|
+
// terminate(), the process teardown) would hang on it. Once
|
|
383
|
+
// woken it runs against the engine, still intact at this
|
|
384
|
+
// point (the bridge is inactive, so a callback it makes
|
|
385
|
+
// passes through), or is refused by Acquire() if the engine
|
|
386
|
+
// has already been unregistered; the destructor's Freeze()
|
|
387
|
+
// waits for it either way.
|
|
388
|
+
s->Abandon();
|
|
389
|
+
gate->Unfreeze();
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
self->DestroyStoppedEngine();
|
|
393
|
+
settle();
|
|
394
|
+
});
|
|
395
|
+
bridge->Release();
|
|
396
|
+
if (!queued) {
|
|
397
|
+
self->DestroyStoppedEngine();
|
|
398
|
+
settle();
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
Napi::Promise promise = worker->Promise();
|
|
402
|
+
stopPromise_ = Napi::Persistent(static_cast<Napi::Object>(promise));
|
|
403
|
+
worker->Queue();
|
|
404
|
+
return promise;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// isLoggedOn(sessionID?): with no argument, true if ANY session of this engine
|
|
408
|
+
// is logged on (FIX::Initiator/Acceptor::isLoggedOn); with a SessionID, true
|
|
409
|
+
// only if THAT session belongs to this engine and is logged on. Always false
|
|
410
|
+
// before start() and once stop() has settled; while a graceful stop() is in
|
|
411
|
+
// progress it still reports the live state (the engine is only destroyed when
|
|
412
|
+
// the stop() promise settles).
|
|
413
|
+
Napi::Value AcceptorWrap::IsLoggedOn(const Napi::CallbackInfo& info) {
|
|
414
|
+
Napi::Env env = info.Env();
|
|
415
|
+
const bool hasId = info.Length() >= 1 && !info[0].IsUndefined();
|
|
416
|
+
// Validate the argument even when the engine is down, for a stable API.
|
|
417
|
+
SessionIDWrap* id =
|
|
418
|
+
hasId ? SessionIDWrap::UnwrapArg(env, info[0], "sessionID") : nullptr;
|
|
419
|
+
if (!acceptor_ || !started_) {
|
|
420
|
+
return Napi::Boolean::New(env, false);
|
|
421
|
+
}
|
|
422
|
+
NQ_TRY(env) {
|
|
423
|
+
if (id == nullptr) {
|
|
424
|
+
return Napi::Boolean::New(env, acceptor_->isLoggedOn());
|
|
425
|
+
}
|
|
426
|
+
FIX::Session* session = acceptor_->getSession(id->SessionID());
|
|
427
|
+
return Napi::Boolean::New(env,
|
|
428
|
+
session != nullptr && session->isLoggedOn());
|
|
429
|
+
} NQ_CATCH(env)
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// getSessions(): SessionID[] -- the sessions this engine was configured with.
|
|
433
|
+
Napi::Value AcceptorWrap::GetSessions(const Napi::CallbackInfo& info) {
|
|
434
|
+
Napi::Env env = info.Env();
|
|
435
|
+
Napi::Array out = Napi::Array::New(env, sessionIDs_.size());
|
|
436
|
+
uint32_t i = 0;
|
|
437
|
+
for (const FIX::SessionID& sid : sessionIDs_) {
|
|
438
|
+
out.Set(i++, SessionIDWrap::NewInstance(env, sid));
|
|
439
|
+
}
|
|
440
|
+
return out;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// getSession(sessionID): Session | undefined -- undefined if the id is not one
|
|
444
|
+
// of this engine's sessions or stop() has settled (its sessions are destroyed
|
|
445
|
+
// then, see Stop()). While stop() is still in progress the sessions are alive
|
|
446
|
+
// and a handle is returned, e.g. for a 'logout' listener reading final seq nums.
|
|
447
|
+
Napi::Value AcceptorWrap::GetSession(const Napi::CallbackInfo& info) {
|
|
448
|
+
Napi::Env env = info.Env();
|
|
449
|
+
SessionIDWrap* id = SessionIDWrap::UnwrapArg(env, info[0], "sessionID");
|
|
450
|
+
if (!acceptor_ || !acceptor_->has(id->SessionID())) {
|
|
451
|
+
return env.Undefined();
|
|
452
|
+
}
|
|
453
|
+
return SessionWrap::NewInstance(env, id->SessionID(), gate_);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
Napi::Value AcceptorWrap::Ref(const Napi::CallbackInfo& info) {
|
|
457
|
+
Napi::Env env = info.Env();
|
|
458
|
+
if (bridge_) bridge_->Ref(env);
|
|
459
|
+
return info.This();
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
Napi::Value AcceptorWrap::Unref(const Napi::CallbackInfo& info) {
|
|
463
|
+
Napi::Env env = info.Env();
|
|
464
|
+
if (bridge_) bridge_->Unref(env);
|
|
465
|
+
return info.This();
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
} // namespace napi_quickfix
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#ifndef NAPI_QUICKFIX_ACCEPTOR_WRAP_H
|
|
2
|
+
#define NAPI_QUICKFIX_ACCEPTOR_WRAP_H
|
|
3
|
+
|
|
4
|
+
#include <napi.h>
|
|
5
|
+
|
|
6
|
+
#include <memory>
|
|
7
|
+
#include <set>
|
|
8
|
+
|
|
9
|
+
#include "quickfix/Log.h"
|
|
10
|
+
#include "quickfix/MessageStore.h"
|
|
11
|
+
#include "quickfix/SessionID.h"
|
|
12
|
+
#include "quickfix/SocketAcceptor.h"
|
|
13
|
+
|
|
14
|
+
#include "application_bridge.h"
|
|
15
|
+
#include "engine_common.h"
|
|
16
|
+
#include "engine_workers.h"
|
|
17
|
+
#include "session_op_gate.h"
|
|
18
|
+
|
|
19
|
+
namespace napi_quickfix {
|
|
20
|
+
|
|
21
|
+
// FIX::SocketAcceptor whose stop() leaves the SocketServer to the acceptor's
|
|
22
|
+
// own network thread. QuickFIX's onStop() runs on the STOPPING thread (the stop
|
|
23
|
+
// worker, or the JS thread in Teardown()) right after FIX::Acceptor::stop()
|
|
24
|
+
// sets the stop flag, and calls m_pServer->close(). That close shuts the
|
|
25
|
+
// listening socket down, which wakes the network thread's poll(), and the
|
|
26
|
+
// network thread -- now seeing the stop flag -- leaves its loop, closes the
|
|
27
|
+
// server again and DELETES it. Nothing orders the two: a stopping thread
|
|
28
|
+
// preempted inside close() resumes on the freed SocketServer and walks its
|
|
29
|
+
// freed socket map (SIGSEGV in std::_Rb_tree_increment, issue #24). Even when
|
|
30
|
+
// it wins, the two close() calls close the same descriptor numbers twice, and
|
|
31
|
+
// the second may hit a descriptor the process has reopened in between.
|
|
32
|
+
//
|
|
33
|
+
// With onStop() a no-op -- as FIX::SocketInitiator's already is -- only the
|
|
34
|
+
// network thread touches the server: it sees the stop flag once its poll()
|
|
35
|
+
// times out (SocketServer's 1 s timeout), and stop() waits for that in its
|
|
36
|
+
// thread join. onStop() is private in FIX::SocketAcceptor but virtual (pure in
|
|
37
|
+
// FIX::Acceptor), so it can be overridden here.
|
|
38
|
+
class SocketAcceptor final : public FIX::SocketAcceptor {
|
|
39
|
+
public:
|
|
40
|
+
using FIX::SocketAcceptor::SocketAcceptor;
|
|
41
|
+
|
|
42
|
+
private:
|
|
43
|
+
void onStop() override {}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Napi::ObjectWrap over FIX::SocketAcceptor. Mirrors InitiatorWrap.
|
|
47
|
+
//
|
|
48
|
+
// new Acceptor({ settings, handlers?, store?, log? })
|
|
49
|
+
class AcceptorWrap : public Napi::ObjectWrap<AcceptorWrap> {
|
|
50
|
+
public:
|
|
51
|
+
static Napi::Object Init(Napi::Env env, Napi::Object exports);
|
|
52
|
+
explicit AcceptorWrap(const Napi::CallbackInfo& info);
|
|
53
|
+
~AcceptorWrap() override;
|
|
54
|
+
|
|
55
|
+
private:
|
|
56
|
+
static Napi::FunctionReference constructor_;
|
|
57
|
+
|
|
58
|
+
Napi::Value Start(const Napi::CallbackInfo& info);
|
|
59
|
+
Napi::Value Stop(const Napi::CallbackInfo& info);
|
|
60
|
+
Napi::Value IsLoggedOn(const Napi::CallbackInfo& info);
|
|
61
|
+
Napi::Value GetSessions(const Napi::CallbackInfo& info);
|
|
62
|
+
Napi::Value GetSession(const Napi::CallbackInfo& info);
|
|
63
|
+
Napi::Value Ref(const Napi::CallbackInfo& info);
|
|
64
|
+
Napi::Value Unref(const Napi::CallbackInfo& info);
|
|
65
|
+
|
|
66
|
+
void Teardown(bool force);
|
|
67
|
+
// Destroy the FIX engine under gate_->Freeze(); destructor only.
|
|
68
|
+
void DestroyEngine();
|
|
69
|
+
// Destroy the FIX engine once stop() has frozen the gate; Stop() only.
|
|
70
|
+
void DestroyStoppedEngine();
|
|
71
|
+
|
|
72
|
+
// Destruct order: acceptor first (declared last), then log, store, bridge.
|
|
73
|
+
std::unique_ptr<ApplicationBridge> bridge_;
|
|
74
|
+
std::unique_ptr<FIX::MessageStoreFactory> storeFactory_;
|
|
75
|
+
// Wraps the real factory so a failed engine constructor can reclaim the
|
|
76
|
+
// engine-level Log QuickFIX would otherwise leak (see engine_common.h).
|
|
77
|
+
std::unique_ptr<TrackingLogFactory> logFactory_;
|
|
78
|
+
// Gate between this engine's destruction and the libuv-thread session
|
|
79
|
+
// operations on ITS sessions (see session_op_gate.h). Shared with the stop
|
|
80
|
+
// worker; registered for sessionIDs_ at construction, unregistered when the
|
|
81
|
+
// engine is destroyed.
|
|
82
|
+
std::shared_ptr<SessionOpGate> gate_ = std::make_shared<SessionOpGate>();
|
|
83
|
+
std::unique_ptr<FIX::SocketAcceptor> acceptor_;
|
|
84
|
+
|
|
85
|
+
// The SessionIDs this engine was configured with, copied at construction so
|
|
86
|
+
// getSessions() still answers after stop() has destroyed the engine.
|
|
87
|
+
std::set<FIX::SessionID> sessionIDs_;
|
|
88
|
+
|
|
89
|
+
bool started_ = false;
|
|
90
|
+
// Set by stop() when it launches its worker (so start() rejects from then
|
|
91
|
+
// on) and by Teardown(); cleared again if that stop() failed. It is NOT
|
|
92
|
+
// "the engine is stopped": a stop() may be in flight.
|
|
93
|
+
bool stopped_ = false;
|
|
94
|
+
bool tornDown_ = false; // Teardown() has run (it runs once)
|
|
95
|
+
bool busy_ = false; // guards against overlapping start/stop AsyncWorkers
|
|
96
|
+
// Signalled by the in-flight stop() worker once its operation has returned;
|
|
97
|
+
// non-null from Stop() launching the worker until that stop settles on the
|
|
98
|
+
// JS thread. Teardown() waits on it (see there).
|
|
99
|
+
std::shared_ptr<StopCompletion> stopSync_;
|
|
100
|
+
// The promise of the stop() in flight, from the call until it settles
|
|
101
|
+
// (which, for a graceful stop, is after its worker's OnOK). A stop() made
|
|
102
|
+
// meanwhile returns this same promise instead of resolving early.
|
|
103
|
+
Napi::ObjectReference stopPromise_;
|
|
104
|
+
|
|
105
|
+
static void CleanupEntry(AcceptorWrap* self);
|
|
106
|
+
|
|
107
|
+
napi_env env_ = nullptr;
|
|
108
|
+
// Runs Teardown on the MAIN thread during environment shutdown, BEFORE N-API
|
|
109
|
+
// finalizes the TSFN. See InitiatorWrap for the full rationale.
|
|
110
|
+
Napi::Env::CleanupHook<void (*)(AcceptorWrap*), AcceptorWrap> cleanupHook_;
|
|
111
|
+
bool cleanupHookFired_ = false;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
} // namespace napi_quickfix
|
|
115
|
+
|
|
116
|
+
#endif // NAPI_QUICKFIX_ACCEPTOR_WRAP_H
|