Skip to content

Commit 69c8756

Browse files
committed
Fix crash at process exit on macOS
fix destructor order by using different way to deal with OrtEnv
1 parent 860d085 commit 69c8756

File tree

5 files changed

+70
-48
lines changed

5 files changed

+70
-48
lines changed

js/node/src/inference_session_wrap.cc

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "common.h"
77
#include "inference_session_wrap.h"
88
#include "ort_instance_data.h"
9+
#include "ort_singleton_data.h"
910
#include "run_options_helper.h"
1011
#include "session_options_helper.h"
1112
#include "tensor_helper.h"
@@ -76,7 +77,7 @@ Napi::Value InferenceSessionWrap::LoadModel(const Napi::CallbackInfo& info) {
7677
Napi::String value = info[0].As<Napi::String>();
7778

7879
ParseSessionOptions(info[1].As<Napi::Object>(), sessionOptions);
79-
this->session_.reset(new Ort::Session(*OrtInstanceData::OrtEnv(),
80+
this->session_.reset(new Ort::Session(*OrtSingletonData::Env(),
8081
#ifdef _WIN32
8182
reinterpret_cast<const wchar_t*>(value.Utf16Value().c_str()),
8283
#else
@@ -91,7 +92,7 @@ Napi::Value InferenceSessionWrap::LoadModel(const Napi::CallbackInfo& info) {
9192
int64_t bytesLength = info[2].As<Napi::Number>().Int64Value();
9293

9394
ParseSessionOptions(info[3].As<Napi::Object>(), sessionOptions);
94-
this->session_.reset(new Ort::Session(*OrtInstanceData::OrtEnv(),
95+
this->session_.reset(new Ort::Session(*OrtSingletonData::Env(),
9596
reinterpret_cast<char*>(buffer) + bytesOffset, bytesLength,
9697
sessionOptions));
9798
} else {
@@ -211,7 +212,7 @@ Napi::Value InferenceSessionWrap::Run(const Napi::CallbackInfo& info) {
211212
ParseRunOptions(info[2].As<Napi::Object>(), runOptions);
212213
}
213214
if (preferredOutputLocations_.size() == 0) {
214-
session_->Run(runOptions == nullptr ? *OrtInstanceData::OrtDefaultRunOptions() : runOptions,
215+
session_->Run(runOptions == nullptr ? *OrtSingletonData::DefaultRunOptions() : runOptions,
215216
inputIndex == 0 ? nullptr : &inputNames_cstr[0], inputIndex == 0 ? nullptr : &inputValues[0],
216217
inputIndex, outputIndex == 0 ? nullptr : &outputNames_cstr[0],
217218
outputIndex == 0 ? nullptr : &outputValues[0], outputIndex);
@@ -240,7 +241,7 @@ Napi::Value InferenceSessionWrap::Run(const Napi::CallbackInfo& info) {
240241
}
241242
}
242243

243-
session_->Run(runOptions == nullptr ? *OrtInstanceData::OrtDefaultRunOptions() : runOptions, *ioBinding_);
244+
session_->Run(runOptions == nullptr ? *OrtSingletonData::DefaultRunOptions() : runOptions, *ioBinding_);
244245

245246
auto outputs = ioBinding_->GetOutputValues();
246247
ORT_NAPI_THROW_ERROR_IF(outputs.size() != outputIndex, env, "Output count mismatch.");

js/node/src/ort_instance_data.cc

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,10 @@
66

77
#include "common.h"
88
#include "ort_instance_data.h"
9+
#include "ort_singleton_data.h"
910
#include "onnxruntime_cxx_api.h"
1011

11-
std::unique_ptr<Ort::Env> OrtInstanceData::ortEnv;
12-
std::unique_ptr<Ort::RunOptions> OrtInstanceData::ortDefaultRunOptions;
13-
std::mutex OrtInstanceData::ortEnvMutex;
14-
std::atomic<uint64_t> OrtInstanceData::ortEnvRefCount;
15-
std::atomic<bool> OrtInstanceData::ortEnvDestroyed;
16-
1712
OrtInstanceData::OrtInstanceData() {
18-
++ortEnvRefCount;
19-
}
20-
21-
OrtInstanceData::~OrtInstanceData() {
22-
if (--ortEnvRefCount == 0) {
23-
std::lock_guard<std::mutex> lock(ortEnvMutex);
24-
if (ortEnv) {
25-
ortDefaultRunOptions.reset(nullptr);
26-
ortEnv.reset();
27-
ortEnvDestroyed = true;
28-
}
29-
}
3013
}
3114

3215
void OrtInstanceData::Create(Napi::Env env, Napi::Function inferenceSessionWrapperFunction) {
@@ -42,14 +25,8 @@ void OrtInstanceData::InitOrt(Napi::Env env, int log_level, Napi::Function tenso
4225

4326
data->ortTensorConstructor = Napi::Persistent(tensorConstructor);
4427

45-
if (!ortEnv) {
46-
std::lock_guard<std::mutex> lock(ortEnvMutex);
47-
if (!ortEnv) {
48-
ORT_NAPI_THROW_ERROR_IF(ortEnvDestroyed, env, "OrtEnv already destroyed.");
49-
ortEnv.reset(new Ort::Env{OrtLoggingLevel(log_level), "onnxruntime-node"});
50-
ortDefaultRunOptions.reset(new Ort::RunOptions{});
51-
}
52-
}
28+
// Only the first time call to OrtSingletonData::GetOrCreateOrtObjects() will create the Ort::Env
29+
OrtSingletonData::GetOrCreateOrtObjects(log_level);
5330
}
5431

5532
const Napi::FunctionReference& OrtInstanceData::TensorConstructor(Napi::Env env) {

js/node/src/ort_instance_data.h

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,6 @@
88

99
/**
1010
* The OrtInstanceData class is designed to manage the lifecycle of necessary instance data, including:
11-
* - The Ort::Env singleton instance.
12-
* This is a global singleton that is shared across all InferenceSessionWrap instances. It is created when the first
13-
* time `InferenceSession.initOrtOnce()` is called. It is destroyed when the last active NAPI Env is destroyed.
14-
* Once destroyed, it cannot be created again.
15-
*
1611
* - The Object reference of the InferenceSessionWrap class and the Tensor constructor.
1712
* This is a per-env data that has the same lifecycle as the Napi::Env. If there are worker threads, each thread will
1813
* have its own handle to the InferenceSessionWrap class and the Tensor constructor.
@@ -27,24 +22,11 @@ struct OrtInstanceData {
2722
static void InitOrt(Napi::Env env, int log_level, Napi::Function tensorConstructor);
2823
// Get the Tensor constructor reference for the Napi::Env
2924
static const Napi::FunctionReference& TensorConstructor(Napi::Env env);
30-
// Get the global Ort::Env
31-
static const Ort::Env* OrtEnv() { return ortEnv.get(); }
32-
// Get the default Ort::RunOptions
33-
static Ort::RunOptions* OrtDefaultRunOptions() { return ortDefaultRunOptions.get(); }
34-
35-
~OrtInstanceData();
3625

3726
private:
3827
OrtInstanceData();
3928

4029
// per env persistent constructors
4130
Napi::FunctionReference wrappedSessionConstructor;
4231
Napi::FunctionReference ortTensorConstructor;
43-
44-
// ORT env (global singleton)
45-
static std::unique_ptr<Ort::Env> ortEnv;
46-
static std::unique_ptr<Ort::RunOptions> ortDefaultRunOptions;
47-
static std::mutex ortEnvMutex;
48-
static std::atomic<uint64_t> ortEnvRefCount;
49-
static std::atomic<bool> ortEnvDestroyed;
5032
};

js/node/src/ort_singleton_data.cc

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
#include "ort_singleton_data.h"
5+
6+
OrtSingletonData::OrtObjects::OrtObjects(int log_level)
7+
: env(new Ort::Env{OrtLoggingLevel(log_level), "onnxruntime-node"}),
8+
default_run_options(new Ort::RunOptions{}) {
9+
}
10+
11+
OrtSingletonData::OrtObjects& OrtSingletonData::GetOrCreateOrtObjects(int log_level) {
12+
static OrtObjects ort_objects(log_level);
13+
return ort_objects;
14+
}
15+
16+
const Ort::Env* OrtSingletonData::Env() {
17+
return GetOrCreateOrtObjects().env.get();
18+
}
19+
20+
const Ort::RunOptions* OrtSingletonData::DefaultRunOptions() {
21+
return GetOrCreateOrtObjects().default_run_options.get();
22+
}

js/node/src/ort_singleton_data.h

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
#pragma once
5+
6+
#include <napi.h>
7+
#include "onnxruntime_cxx_api.h"
8+
9+
/**
10+
* The OrtSingletonData class is designed to manage the lifecycle of necessary singleton instance data, including:
11+
*
12+
* - The Ort::Env singleton instance.
13+
* This is a global singleton that is shared across all InferenceSessionWrap instances. It is created when the first
14+
* time `InferenceSession.initOrtOnce()` is called.
15+
*
16+
* - The Ort::RunOptions singleton instance.
17+
* This is an empty default RunOptions instance. It is created once to allow reuse across all session inference run.
18+
*
19+
* The OrtSingletonData class uses the "Meyers Singleton" pattern to ensure thread-safe lazy initialization, as well as
20+
* proper destruction order at program exit.
21+
*/
22+
struct OrtSingletonData {
23+
struct OrtObjects {
24+
std::unique_ptr<Ort::Env> env;
25+
std::unique_ptr<Ort::RunOptions> default_run_options;
26+
27+
private:
28+
// The following pattern ensures that OrtObjects can only be created by OrtSingletonData
29+
OrtObjects(int log_level);
30+
friend struct OrtSingletonData;
31+
};
32+
33+
static OrtObjects& GetOrCreateOrtObjects(int log_level = ORT_LOGGING_LEVEL_WARNING);
34+
35+
// Get the global Ort::Env
36+
static const Ort::Env* Env();
37+
38+
// Get the default Ort::RunOptions
39+
static const Ort::RunOptions* DefaultRunOptions();
40+
};

0 commit comments

Comments
 (0)