Skip to content

Commit 3383133

Browse files
Introduced Connection Epoch concept to not finalize stale objects
1 parent 3ca6806 commit 3383133

File tree

12 files changed

+96
-27
lines changed

12 files changed

+96
-27
lines changed

obs-studio-client/source/audio-encoder.cpp

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ osn::AudioEncoder::AudioEncoder(const Napi::CallbackInfo &info) : Napi::ObjectWr
4848
size_t length = info.Length();
4949
this->uid = 0;
5050
this->encoderInitialized = false;
51+
this->connectionEpoch = 0;
5152

5253
if (length <= 0 || !info[0].IsNumber()) {
5354
Napi::TypeError::New(env, "Number expected").ThrowAsJavaScriptException();
@@ -56,6 +57,7 @@ osn::AudioEncoder::AudioEncoder(const Napi::CallbackInfo &info) : Napi::ObjectWr
5657

5758
this->uid = (uint64_t)info[0].ToNumber().Int64Value();
5859
this->encoderInitialized = true;
60+
this->connectionEpoch = Controller::GetInstance().GetConnectionEpoch();
5961
}
6062

6163
Napi::Value osn::AudioEncoder::Create(const Napi::CallbackInfo &info)
@@ -87,15 +89,23 @@ void osn::AudioEncoder::Finalize(Napi::Env env)
8789
if (!this->encoderInitialized)
8890
return;
8991

90-
auto conn = GetConnection(env);
91-
if (!conn)
92+
// If OBS was restarted/disconnected, skip IPC cleanup for this stale wrapper.
93+
if (this->connectionEpoch != Controller::GetInstance().GetConnectionEpoch()) {
94+
this->encoderInitialized = false;
95+
this->uid = UINT64_MAX;
9296
return;
97+
}
9398

94-
std::vector<ipc::value> response = conn->call_synchronous_helper("AudioEncoder", "Finalize", {ipc::value(this->uid)});
99+
auto conn = Controller::GetInstance().GetConnection();
100+
if (!conn) {
101+
this->encoderInitialized = false;
102+
this->uid = UINT64_MAX;
103+
return;
104+
}
105+
106+
conn->call_synchronous_helper("AudioEncoder", "Finalize", {ipc::value(this->uid)});
95107
this->encoderInitialized = false;
96108
this->uid = UINT64_MAX;
97-
if (!ValidateResponse(env, response))
98-
return;
99109
}
100110

101111
void osn::AudioEncoder::Release(const Napi::CallbackInfo &info)
@@ -160,4 +170,4 @@ void osn::AudioEncoder::SetBitrate(const Napi::CallbackInfo &info, const Napi::V
160170
return;
161171

162172
conn->call("AudioEncoder", "SetBitrate", {ipc::value(this->uid), ipc::value(value.ToNumber().Uint32Value())});
163-
}
173+
}

obs-studio-client/source/audio-encoder.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class AudioEncoder : public Napi::ObjectWrap<osn::AudioEncoder> {
2525
public:
2626
uint64_t uid;
2727
bool encoderInitialized;
28+
uint64_t connectionEpoch;
2829

2930
public:
3031
static Napi::FunctionReference constructor;
@@ -40,4 +41,4 @@ class AudioEncoder : public Napi::ObjectWrap<osn::AudioEncoder> {
4041
Napi::Value GetBitrate(const Napi::CallbackInfo &info);
4142
void SetBitrate(const Napi::CallbackInfo &info, const Napi::Value &value);
4243
};
43-
}
44+
}

obs-studio-client/source/controller.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,8 @@ std::shared_ptr<ipc::client> Controller::connect(const std::string &uri)
372372
}
373373

374374
m_connection = cl;
375+
// Bump epoch on successful connect to invalidate stale native wrappers.
376+
m_connectionEpoch.fetch_add(1, std::memory_order_relaxed);
375377
return m_connection;
376378
}
377379

@@ -382,6 +384,8 @@ void Controller::disconnect()
382384
m_isServer = false;
383385
}
384386
m_connection = nullptr;
387+
// Bump epoch on disconnect so finalizers skip IPC calls for old sessions.
388+
m_connectionEpoch.fetch_add(1, std::memory_order_relaxed);
385389
}
386390

387391
DWORD Controller::GetExitCode()
@@ -394,6 +398,11 @@ std::shared_ptr<ipc::client> Controller::GetConnection()
394398
return m_connection;
395399
}
396400

401+
uint64_t Controller::GetConnectionEpoch() const
402+
{
403+
return m_connectionEpoch.load(std::memory_order_relaxed);
404+
}
405+
397406
Napi::Value js_setServerPath(const Napi::CallbackInfo &info)
398407
{
399408
if (info.Length() == 0) {

obs-studio-client/source/controller.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
******************************************************************************/
1818

1919
#pragma once
20+
#include <atomic>
2021
#include <memory>
2122
#include <map>
2223
#include <string>
@@ -52,9 +53,13 @@ class Controller {
5253
void disconnect();
5354

5455
std::shared_ptr<ipc::client> GetConnection();
56+
uint64_t GetConnectionEpoch() const;
5557

5658
private:
5759
bool m_isServer = false;
5860
std::shared_ptr<ipc::client> m_connection;
5961
ipc::ProcessInfo procId;
62+
// Monotonic "epoch" for the IPC connection lifecycle. Helps to detect stale wrappers
63+
// after disconnect/reconnect so finalizers can safely no-op.
64+
std::atomic<uint64_t> m_connectionEpoch{0};
6065
};

obs-studio-client/source/delay.cpp

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,15 @@ osn::Delay::Delay(const Napi::CallbackInfo &info) : Napi::ObjectWrap<osn::Delay>
4646
Napi::HandleScope scope(env);
4747
size_t length = info.Length();
4848
this->uid = 0;
49+
this->connectionEpoch = 0;
4950

5051
if (length <= 0 || !info[0].IsNumber()) {
5152
Napi::TypeError::New(env, "Number expected").ThrowAsJavaScriptException();
5253
return;
5354
}
5455

5556
this->uid = (uint64_t)info[0].ToNumber().Int64Value();
57+
this->connectionEpoch = Controller::GetInstance().GetConnectionEpoch();
5658
}
5759

5860
Napi::Value osn::Delay::Create(const Napi::CallbackInfo &info)
@@ -73,12 +75,20 @@ Napi::Value osn::Delay::Create(const Napi::CallbackInfo &info)
7375

7476
void osn::Delay::Finalize(Napi::Env env)
7577
{
76-
auto conn = GetConnection(env);
77-
if (!conn)
78+
if (this->uid == UINT64_MAX)
7879
return;
7980

80-
if (this->uid == UINT64_MAX)
81+
// If OBS was restarted/disconnected, skip IPC cleanup for this stale wrapper.
82+
if (this->connectionEpoch != Controller::GetInstance().GetConnectionEpoch()) {
83+
this->uid = UINT64_MAX;
84+
return;
85+
}
86+
87+
auto conn = Controller::GetInstance().GetConnection();
88+
if (!conn) {
89+
this->uid = UINT64_MAX;
8190
return;
91+
}
8292

8393
conn->call_synchronous_helper("Delay", "Destroy", {ipc::value(this->uid)});
8494

@@ -152,4 +162,4 @@ void osn::Delay::SetPreserveDelay(const Napi::CallbackInfo &info, const Napi::Va
152162
return;
153163

154164
conn->call_synchronous_helper("Delay", "SetPreserveDelay", {ipc::value(this->uid), ipc::value(value.ToBoolean().Value())});
155-
}
165+
}

obs-studio-client/source/delay.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ namespace osn {
2323
class Delay : public Napi::ObjectWrap<osn::Delay> {
2424
public:
2525
uint64_t uid;
26+
uint64_t connectionEpoch;
2627

2728
public:
2829
static Napi::FunctionReference constructor;
@@ -38,4 +39,4 @@ class Delay : public Napi::ObjectWrap<osn::Delay> {
3839
Napi::Value GetPreserveDelay(const Napi::CallbackInfo &info);
3940
void SetPreserveDelay(const Napi::CallbackInfo &info, const Napi::Value &value);
4041
};
41-
}
42+
}

obs-studio-client/source/network.cpp

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,15 @@ osn::Network::Network(const Napi::CallbackInfo &info) : Napi::ObjectWrap<osn::Ne
4949
Napi::HandleScope scope(env);
5050
size_t length = info.Length();
5151
this->uid = UINT64_MAX;
52+
this->connectionEpoch = 0;
5253

5354
if (length <= 0 || !info[0].IsNumber()) {
5455
Napi::TypeError::New(env, "Number expected").ThrowAsJavaScriptException();
5556
return;
5657
}
5758

5859
this->uid = (uint64_t)info[0].ToNumber().Int64Value();
60+
this->connectionEpoch = Controller::GetInstance().GetConnectionEpoch();
5961
}
6062

6163
Napi::Value osn::Network::Create(const Napi::CallbackInfo &info)
@@ -76,12 +78,20 @@ Napi::Value osn::Network::Create(const Napi::CallbackInfo &info)
7678

7779
void osn::Network::Finalize(Napi::Env env)
7880
{
79-
auto conn = GetConnection(env);
80-
if (!conn)
81+
if (this->uid == UINT64_MAX)
8182
return;
8283

83-
if (this->uid == UINT64_MAX)
84+
// If OBS was restarted/disconnected, skip IPC cleanup for this stale wrapper.
85+
if (this->connectionEpoch != Controller::GetInstance().GetConnectionEpoch()) {
86+
this->uid = UINT64_MAX;
8487
return;
88+
}
89+
90+
auto conn = Controller::GetInstance().GetConnection();
91+
if (!conn) {
92+
this->uid = UINT64_MAX;
93+
return;
94+
}
8595

8696
conn->call_synchronous_helper("Network", "Destroy", {ipc::value(this->uid)});
8797

obs-studio-client/source/network.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ namespace osn {
2323
class Network : public Napi::ObjectWrap<osn::Network> {
2424
public:
2525
uint64_t uid;
26+
uint64_t connectionEpoch;
2627

2728
public:
2829
static Napi::FunctionReference constructor;
@@ -41,4 +42,4 @@ class Network : public Napi::ObjectWrap<osn::Network> {
4142
Napi::Value GetEnableLowLatency(const Napi::CallbackInfo &info);
4243
void SetEnableLowLatency(const Napi::CallbackInfo &info, const Napi::Value &value);
4344
};
44-
}
45+
}

obs-studio-client/source/reconnect.cpp

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,15 @@ osn::Reconnect::Reconnect(const Napi::CallbackInfo &info) : Napi::ObjectWrap<osn
4646
Napi::HandleScope scope(env);
4747
size_t length = info.Length();
4848
this->uid = 0;
49+
this->connectionEpoch = 0;
4950

5051
if (length <= 0 || !info[0].IsNumber()) {
5152
Napi::TypeError::New(env, "Number expected").ThrowAsJavaScriptException();
5253
return;
5354
}
5455

5556
this->uid = (uint64_t)info[0].ToNumber().Int64Value();
57+
this->connectionEpoch = Controller::GetInstance().GetConnectionEpoch();
5658
}
5759

5860
Napi::Value osn::Reconnect::Create(const Napi::CallbackInfo &info)
@@ -73,11 +75,19 @@ Napi::Value osn::Reconnect::Create(const Napi::CallbackInfo &info)
7375

7476
void osn::Reconnect::Finalize(Napi::Env env)
7577
{
76-
auto conn = GetConnection(env);
77-
if (!conn)
78-
return;
79-
8078
if (this->uid != UINT64_MAX) {
79+
// If OBS was restarted/disconnected, skip IPC cleanup for this stale wrapper.
80+
if (this->connectionEpoch != Controller::GetInstance().GetConnectionEpoch()) {
81+
this->uid = UINT64_MAX;
82+
return;
83+
}
84+
85+
auto conn = Controller::GetInstance().GetConnection();
86+
if (!conn) {
87+
this->uid = UINT64_MAX;
88+
return;
89+
}
90+
8191
conn->call_synchronous_helper("Reconnect", "Destroy", {ipc::value(this->uid)});
8292
this->uid = UINT64_MAX;
8393
}

obs-studio-client/source/reconnect.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ namespace osn {
2323
class Reconnect : public Napi::ObjectWrap<osn::Reconnect> {
2424
public:
2525
uint64_t uid;
26+
uint64_t connectionEpoch;
2627

2728
public:
2829
static Napi::FunctionReference constructor;
@@ -39,4 +40,4 @@ class Reconnect : public Napi::ObjectWrap<osn::Reconnect> {
3940
Napi::Value GetMaxRetries(const Napi::CallbackInfo &info);
4041
void SetMaxRetries(const Napi::CallbackInfo &info, const Napi::Value &value);
4142
};
42-
}
43+
}

0 commit comments

Comments
 (0)