Skip to content

Commit 8e392f2

Browse files
summeroffclaude
andcommitted
fix: do not release a claim while its output is still writing
Two windows where the claim was dropped early, both found in review. A failed obs_output_start() does not mean the output is idle: obs_output_can_begin_data_capture() refuses while active(output), so calling start() twice lands in the failure path with the first muxer still writing. Only release when the output is genuinely not active. Both recording Destroy handlers deregistered the object before deleting it, and only the destructor called DeleteOutput(), which can wait up to 20s for the muxer to drain. For that whole window the file was still being written while its claim was invisible to a concurrent Start. Stop first, then deregister; DeleteOutput() is idempotent so the destructor's call becomes a no-op. The on-disk check covers most of both windows -- the file exists by then -- but not when overwrite is set, which suppresses exactly that check. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 01e3c2a commit 8e392f2

3 files changed

Lines changed: 16 additions & 2 deletions

File tree

obs-studio-server/source/osn-advanced-recording.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@ void osn::IAdvancedRecording::Destroy(void *data, const int64_t id, const std::v
9393
PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Recording reference is not valid.");
9494
}
9595

96+
// Stop before deregistering. The destructor would do this anyway, but by then the object is out
97+
// of the manager, and DeleteOutput() can wait up to 20s for the muxer to drain -- a window where
98+
// the file is still being written but its claim is invisible to a concurrent Start.
99+
recording->DeleteOutput();
100+
96101
osn::IAdvancedRecording::Manager::GetInstance().free(recording);
97102
delete recording;
98103

obs-studio-server/source/osn-output.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,12 @@ void osn::Output::StartOutput()
179179
code = OBS_OUTPUT_ERROR;
180180
}
181181

182-
// libobs emits no "stop" here, so OnStopped() never runs for a failed start.
183-
OnOutputStopped();
182+
// libobs emits no "stop" here, so OnStopped() never runs for a failed start. But a failed start
183+
// does not imply an idle output: obs_output_can_begin_data_capture() refuses while the output is
184+
// already active, so starting twice lands here with the first muxer still writing. Releasing then
185+
// would unclaim a file that is still being written to.
186+
if (!obs_output_active(m_output))
187+
OnOutputStopped();
184188

185189
PushReceivedSignal("stop", code, errorMessage);
186190
}

obs-studio-server/source/osn-simple-recording.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ void osn::ISimpleRecording::Destroy(void *data, const int64_t id, const std::vec
8888
PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Recording reference is not valid.");
8989
}
9090

91+
// Stop before deregistering. The destructor would do this anyway, but by then the object is out
92+
// of the manager, and DeleteOutput() can wait up to 20s for the muxer to drain -- a window where
93+
// the file is still being written but its claim is invisible to a concurrent Start.
94+
recording->DeleteOutput();
95+
9196
osn::ISimpleRecording::Manager::GetInstance().free(recording);
9297
delete recording;
9398

0 commit comments

Comments
 (0)