mirror of
https://github.com/baketnk/frame-yap.git
synced 2026-10-06 03:00:04 +02:00
Add opt-in delivery-debug.log for paced text delivery
Advanced debugging now also records paced-delivery metadata (byte offsets, lease/send errors, finish results and the exact focus-guard check that failed) to an owner-private delivery-debug.log. No transcript text is logged. Diagnoses later chunks being dropped after the first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
279455134b
commit
045cd4f45f
8 files changed
+136
-34
No files matched your search
@@ -112,6 +112,12 @@ is required to avoid competing rotation by multiple workers. Debug output never
|
|||||||
shares the framed protocol stdout. Direct worker CLI use with `--advanced-debug`
|
shares the framed protocol stdout. Direct worker CLI use with `--advanced-debug`
|
||||||
writes to its caller's stderr; the native adapter supplies the private bounded sink.
|
writes to its caller's stderr; the native adapter supplies the private bounded sink.
|
||||||
|
|
||||||
|
The same opt-in also writes `delivery-debug.log` (rotated to
|
||||||
|
`delivery-debug.previous.log` when debugging is enabled, capped near 1 MiB) in
|
||||||
|
that directory: paced-delivery metadata only — start/sent byte counts and
|
||||||
|
offsets, lease/send errors, finish results and which focus-guard check failed.
|
||||||
|
It never contains transcript text.
|
||||||
|
|
||||||
Hardware-free tests run through CTest, including fake-child cancellation, short
|
Hardware-free tests run through CTest, including fake-child cancellation, short
|
||||||
injected warmup/request deadlines, duplicate/stale replies, malformed frames,
|
injected warmup/request deadlines, duplicate/stale replies, malformed frames,
|
||||||
missing/hash-mismatched model files and symlink refusal. Default production
|
missing/hash-mismatched model files and symlink refusal. Default production
|
||||||
|
|||||||
+21
-11
@@ -21,6 +21,7 @@ struct FocusGuard::Impl {
|
|||||||
bool attempted = false;
|
bool attempted = false;
|
||||||
bool armed = false;
|
bool armed = false;
|
||||||
bool dead = false;
|
bool dead = false;
|
||||||
|
const char* reason = "none";
|
||||||
|
|
||||||
~Impl() { if (connection) xcb_disconnect(connection); }
|
~Impl() { if (connection) xcb_disconnect(connection); }
|
||||||
|
|
||||||
@@ -80,10 +81,12 @@ struct FocusGuard::Impl {
|
|||||||
|
|
||||||
bool snapshot(xcb_window_t& current) {
|
bool snapshot(xcb_window_t& current) {
|
||||||
xcb_window_t active = XCB_WINDOW_NONE, gamescope = XCB_WINDOW_NONE, actual = XCB_WINDOW_NONE;
|
xcb_window_t active = XCB_WINDOW_NONE, gamescope = XCB_WINDOW_NONE, actual = XCB_WINDOW_NONE;
|
||||||
if (!property_window(active_atom, XCB_ATOM_WINDOW, active) ||
|
if (!property_window(active_atom, XCB_ATOM_WINDOW, active)) { reason = "no _NET_ACTIVE_WINDOW"; return false; }
|
||||||
!property_window(gamescope_atom, XCB_ATOM_CARDINAL, gamescope) ||
|
if (!property_window(gamescope_atom, XCB_ATOM_CARDINAL, gamescope)) { reason = "no GAMESCOPE_FOCUSED_WINDOW"; return false; }
|
||||||
!focus(actual) || active != gamescope || active != actual || !keys_up())
|
if (!focus(actual)) { reason = "no X input focus"; return false; }
|
||||||
return false;
|
if (active != gamescope) { reason = "active != gamescope focus"; return false; }
|
||||||
|
if (active != actual) { reason = "active != X input focus"; return false; }
|
||||||
|
if (!keys_up()) { reason = "key held"; return false; }
|
||||||
current = active;
|
current = active;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -92,15 +95,17 @@ struct FocusGuard::Impl {
|
|||||||
while (xcb_generic_event_t* event = xcb_poll_for_event(connection)) {
|
while (xcb_generic_event_t* event = xcb_poll_for_event(connection)) {
|
||||||
const uint8_t type = event->response_type & 0x7f;
|
const uint8_t type = event->response_type & 0x7f;
|
||||||
bool bad = type == 0; // asynchronous X error
|
bool bad = type == 0; // asynchronous X error
|
||||||
|
if (bad) reason = "X error";
|
||||||
if (type == XCB_PROPERTY_NOTIFY) {
|
if (type == XCB_PROPERTY_NOTIFY) {
|
||||||
const auto* e = reinterpret_cast<xcb_property_notify_event_t*>(event);
|
const auto* e = reinterpret_cast<xcb_property_notify_event_t*>(event);
|
||||||
if (e->window == root && (e->atom == active_atom || e->atom == gamescope_atom)) bad = true;
|
if (e->window == root && e->atom == active_atom) { bad = true; reason = "_NET_ACTIVE_WINDOW changed"; }
|
||||||
|
if (e->window == root && e->atom == gamescope_atom) { bad = true; reason = "GAMESCOPE_FOCUSED_WINDOW changed"; }
|
||||||
} else if (type == XCB_FOCUS_OUT) {
|
} else if (type == XCB_FOCUS_OUT) {
|
||||||
const auto* e = reinterpret_cast<xcb_focus_out_event_t*>(event);
|
const auto* e = reinterpret_cast<xcb_focus_out_event_t*>(event);
|
||||||
if (e->event == target) bad = true;
|
if (e->event == target) { bad = true; reason = "FocusOut on target"; }
|
||||||
} else if (type == XCB_DESTROY_NOTIFY) {
|
} else if (type == XCB_DESTROY_NOTIFY) {
|
||||||
const auto* e = reinterpret_cast<xcb_destroy_notify_event_t*>(event);
|
const auto* e = reinterpret_cast<xcb_destroy_notify_event_t*>(event);
|
||||||
if (e->window == target) bad = true;
|
if (e->window == target) { bad = true; reason = "target destroyed"; }
|
||||||
} else if (type == XCB_CREATE_NOTIFY || type == XCB_UNMAP_NOTIFY ||
|
} else if (type == XCB_CREATE_NOTIFY || type == XCB_UNMAP_NOTIFY ||
|
||||||
type == XCB_MAP_NOTIFY || type == XCB_MAP_REQUEST ||
|
type == XCB_MAP_NOTIFY || type == XCB_MAP_REQUEST ||
|
||||||
type == XCB_REPARENT_NOTIFY || type == XCB_CONFIGURE_NOTIFY ||
|
type == XCB_REPARENT_NOTIFY || type == XCB_CONFIGURE_NOTIFY ||
|
||||||
@@ -122,18 +127,22 @@ struct FocusGuard::Impl {
|
|||||||
case XCB_CIRCULATE_NOTIFY: window = reinterpret_cast<xcb_circulate_notify_event_t*>(event)->window; break;
|
case XCB_CIRCULATE_NOTIFY: window = reinterpret_cast<xcb_circulate_notify_event_t*>(event)->window; break;
|
||||||
case XCB_CIRCULATE_REQUEST: window = reinterpret_cast<xcb_circulate_request_event_t*>(event)->window; break;
|
case XCB_CIRCULATE_REQUEST: window = reinterpret_cast<xcb_circulate_request_event_t*>(event)->window; break;
|
||||||
}
|
}
|
||||||
if (window == target) bad = true;
|
if (window == target) { bad = true; reason = "structure event on target"; }
|
||||||
}
|
}
|
||||||
std::free(event);
|
std::free(event);
|
||||||
if (bad) return false;
|
if (bad) return false;
|
||||||
}
|
}
|
||||||
return !failed();
|
if (failed()) { reason = "X connection failed"; return false; }
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool check() {
|
bool check() {
|
||||||
if (!armed || failed() || !drain_events()) { dead = true; return false; }
|
if (!armed || dead) { reason = armed ? reason : "not armed"; dead = true; return false; }
|
||||||
|
if (failed() || !drain_events()) { dead = true; return false; }
|
||||||
xcb_window_t current = XCB_WINDOW_NONE;
|
xcb_window_t current = XCB_WINDOW_NONE;
|
||||||
if (!snapshot(current) || current != target || !drain_events()) {
|
if (!snapshot(current)) { dead = true; return false; }
|
||||||
|
if (current != target) { reason = "focus moved to another window"; dead = true; return false; }
|
||||||
|
if (!drain_events()) {
|
||||||
dead = true;
|
dead = true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -178,6 +187,7 @@ bool FocusGuard::arm() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool FocusGuard::valid() { return impl_->check(); }
|
bool FocusGuard::valid() { return impl_->check(); }
|
||||||
|
const char* FocusGuard::failure() const { return impl_->reason; }
|
||||||
void FocusGuard::invalidate() { impl_->dead = true; impl_->armed = false; }
|
void FocusGuard::invalidate() { impl_->dead = true; impl_->armed = false; }
|
||||||
|
|
||||||
} // namespace frameyap
|
} // namespace frameyap
|
||||||
@@ -18,6 +18,8 @@ public:
|
|||||||
bool arm();
|
bool arm();
|
||||||
bool valid();
|
bool valid();
|
||||||
void invalidate();
|
void invalidate();
|
||||||
|
// Static label of the most recent failed check, for opt-in diagnostics.
|
||||||
|
const char* failure() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Impl;
|
struct Impl;
|
||||||
|
|||||||
+35
-4
@@ -9,6 +9,9 @@ PacedDelivery::PacedDelivery(DeliveryFactory acquire, Clock clock,
|
|||||||
: acquire_(std::move(acquire)), clock_(std::move(clock)),
|
: acquire_(std::move(acquire)), clock_(std::move(clock)),
|
||||||
release_idle_(std::move(release_idle)), permitted_(std::move(permitted)) {}
|
release_idle_(std::move(release_idle)), permitted_(std::move(permitted)) {}
|
||||||
|
|
||||||
|
void PacedDelivery::trace(const std::string& line) const {
|
||||||
|
if (trace_) { try { trace_(line); } catch (...) {} }
|
||||||
|
}
|
||||||
bool PacedDelivery::valid_guard() const {
|
bool PacedDelivery::valid_guard() const {
|
||||||
try { return (!permitted_ || permitted_()) && task_->guard && task_->guard(); }
|
try { return (!permitted_ || permitted_()) && task_->guard && task_->guard(); }
|
||||||
catch (...) { return false; }
|
catch (...) { return false; }
|
||||||
@@ -32,15 +35,25 @@ void PacedDelivery::start(std::string text, bool enter, Guard guard,
|
|||||||
candidate->first_lease = acquire_();
|
candidate->first_lease = acquire_();
|
||||||
if (!candidate->first_lease || (permitted_ && !permitted_()) || !candidate->guard())
|
if (!candidate->first_lease || (permitted_ && !permitted_()) || !candidate->guard())
|
||||||
throw std::runtime_error("Input focus changed during acquisition");
|
throw std::runtime_error("Input focus changed during acquisition");
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
trace(std::string("start rejected: ") + e.what());
|
||||||
|
std::fill(candidate->text.begin(), candidate->text.end(), '\0');
|
||||||
|
throw;
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
|
trace("start rejected: unknown");
|
||||||
std::fill(candidate->text.begin(), candidate->text.end(), '\0');
|
std::fill(candidate->text.begin(), candidate->text.end(), '\0');
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
trace("start bytes=" + std::to_string(candidate->text.size()) +
|
||||||
|
" enter=" + (candidate->enter ? "1" : "0"));
|
||||||
retained_ = true;
|
retained_ = true;
|
||||||
task_ = std::move(candidate);
|
task_ = std::move(candidate);
|
||||||
}
|
}
|
||||||
PacedDelivery::Outcome PacedDelivery::finish(DeliveryResult result, bool focus_lost) {
|
PacedDelivery::Outcome PacedDelivery::finish(DeliveryResult result, bool focus_lost) {
|
||||||
Outcome out{result, task_->began, focus_lost};
|
Outcome out{result, task_->began, focus_lost};
|
||||||
|
trace("finish result=" + std::to_string(static_cast<int>(result)) + " began=" +
|
||||||
|
(task_->began ? "1" : "0") + " focus_lost=" + (focus_lost ? "1" : "0") +
|
||||||
|
" offset=" + std::to_string(task_->offset) + "/" + std::to_string(task_->text.size()));
|
||||||
std::fill(task_->text.begin(), task_->text.end(), '\0');
|
std::fill(task_->text.begin(), task_->text.end(), '\0');
|
||||||
task_.reset();
|
task_.reset();
|
||||||
return out;
|
return out;
|
||||||
@@ -59,15 +72,25 @@ std::optional<PacedDelivery::Outcome> PacedDelivery::tick() {
|
|||||||
auto& task = *task_;
|
auto& task = *task_;
|
||||||
// A focus loss before ANY send leaves the review unconsumed. After the
|
// A focus loss before ANY send leaves the review unconsumed. After the
|
||||||
// first possible send, the remainder is discarded, not retargeted/retried.
|
// first possible send, the remainder is discarded, not retargeted/retried.
|
||||||
if (!valid_guard()) return finish(task.began ? DeliveryResult::TextUncertain : DeliveryResult::Ignored, true);
|
if (!valid_guard()) {
|
||||||
|
trace("guard failed before acquire");
|
||||||
|
return finish(task.began ? DeliveryResult::TextUncertain : DeliveryResult::Ignored, true);
|
||||||
|
}
|
||||||
std::unique_ptr<DeliveryLease> lease = std::move(task.first_lease);
|
std::unique_ptr<DeliveryLease> lease = std::move(task.first_lease);
|
||||||
if (!lease) {
|
if (!lease) {
|
||||||
try { lease = acquire_(); }
|
try { lease = acquire_(); }
|
||||||
catch (...) { return finish(task.began ? (task.offset == task.text.size() ?
|
catch (const std::exception& e) {
|
||||||
|
trace(std::string("acquire failed: ") + e.what());
|
||||||
|
return finish(task.began ? (task.offset == task.text.size() ?
|
||||||
|
DeliveryResult::TextQueuedEnterUnavailable : DeliveryResult::TextUncertain) : DeliveryResult::Ignored); }
|
||||||
|
catch (...) { trace("acquire failed: unknown"); return finish(task.began ? (task.offset == task.text.size() ?
|
||||||
DeliveryResult::TextQueuedEnterUnavailable : DeliveryResult::TextUncertain) : DeliveryResult::Ignored); }
|
DeliveryResult::TextQueuedEnterUnavailable : DeliveryResult::TextUncertain) : DeliveryResult::Ignored); }
|
||||||
if (!lease) return finish(task.began ? DeliveryResult::TextUncertain : DeliveryResult::Ignored);
|
if (!lease) return finish(task.began ? DeliveryResult::TextUncertain : DeliveryResult::Ignored);
|
||||||
}
|
}
|
||||||
if (!valid_guard()) return finish(task.began ? DeliveryResult::TextUncertain : DeliveryResult::Ignored, true);
|
if (!valid_guard()) {
|
||||||
|
trace("guard failed after acquire");
|
||||||
|
return finish(task.began ? DeliveryResult::TextUncertain : DeliveryResult::Ignored, true);
|
||||||
|
}
|
||||||
if (!task.began) {
|
if (!task.began) {
|
||||||
// A callback consumes review immediately before a possibly ambiguous send.
|
// A callback consumes review immediately before a possibly ambiguous send.
|
||||||
try { if (task.on_first_send) task.on_first_send(); }
|
try { if (task.on_first_send) task.on_first_send(); }
|
||||||
@@ -87,17 +110,25 @@ std::optional<PacedDelivery::Outcome> PacedDelivery::tick() {
|
|||||||
try {
|
try {
|
||||||
chunk = task.text.substr(task.offset, end - task.offset);
|
chunk = task.text.substr(task.offset, end - task.offset);
|
||||||
lease->text(chunk);
|
lease->text(chunk);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
trace("text send failed at offset=" + std::to_string(task.offset) + ": " + e.what());
|
||||||
|
last_commit_ = clock_();
|
||||||
|
std::fill(chunk.begin(), chunk.end(), '\0');
|
||||||
|
return finish(DeliveryResult::TextUncertain);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
|
trace("text send failed at offset=" + std::to_string(task.offset) + ": unknown");
|
||||||
last_commit_ = clock_();
|
last_commit_ = clock_();
|
||||||
std::fill(chunk.begin(), chunk.end(), '\0');
|
std::fill(chunk.begin(), chunk.end(), '\0');
|
||||||
return finish(DeliveryResult::TextUncertain);
|
return finish(DeliveryResult::TextUncertain);
|
||||||
}
|
}
|
||||||
last_commit_ = clock_();
|
last_commit_ = clock_();
|
||||||
|
trace("sent offset=" + std::to_string(task.offset) + " bytes=" + std::to_string(end - task.offset));
|
||||||
std::fill(chunk.begin(), chunk.end(), '\0');
|
std::fill(chunk.begin(), chunk.end(), '\0');
|
||||||
task.offset = end;
|
task.offset = end;
|
||||||
if (end == task.text.size() && !task.enter) return finish(DeliveryResult::TextQueued);
|
if (end == task.text.size() && !task.enter) return finish(DeliveryResult::TextQueued);
|
||||||
} else {
|
} else {
|
||||||
try { lease->enter(); }
|
try { lease->enter(); trace("sent enter"); }
|
||||||
|
catch (const std::exception& e) { trace(std::string("enter failed: ") + e.what()); last_commit_ = clock_(); return finish(DeliveryResult::EnterUncertain); }
|
||||||
catch (...) { last_commit_ = clock_(); return finish(DeliveryResult::EnterUncertain); }
|
catch (...) { last_commit_ = clock_(); return finish(DeliveryResult::EnterUncertain); }
|
||||||
last_commit_ = clock_();
|
last_commit_ = clock_();
|
||||||
return finish(DeliveryResult::EnterQueued);
|
return finish(DeliveryResult::EnterQueued);
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ class PacedDelivery {
|
|||||||
public:
|
public:
|
||||||
using Clock = std::function<std::chrono::steady_clock::time_point()>;
|
using Clock = std::function<std::chrono::steady_clock::time_point()>;
|
||||||
using Guard = std::function<bool()>;
|
using Guard = std::function<bool()>;
|
||||||
|
// Opt-in diagnostics: metadata only (sizes, offsets, reasons), never text.
|
||||||
|
using Trace = std::function<void(const std::string&)>;
|
||||||
struct Outcome {
|
struct Outcome {
|
||||||
DeliveryResult result;
|
DeliveryResult result;
|
||||||
bool began; // first send may have reached the compositor
|
bool began; // first send may have reached the compositor
|
||||||
@@ -32,6 +34,7 @@ public:
|
|||||||
bool active() const { return bool(task_); }
|
bool active() const { return bool(task_); }
|
||||||
bool began() const { return task_ && task_->began; }
|
bool began() const { return task_ && task_->began; }
|
||||||
void cancel(); // drops and scrubs the remaining literal; never sends Enter
|
void cancel(); // drops and scrubs the remaining literal; never sends Enter
|
||||||
|
void set_trace(Trace trace) { trace_ = std::move(trace); }
|
||||||
private:
|
private:
|
||||||
struct Task {
|
struct Task {
|
||||||
std::string text;
|
std::string text;
|
||||||
@@ -44,10 +47,12 @@ private:
|
|||||||
bool valid_guard() const;
|
bool valid_guard() const;
|
||||||
Outcome finish(DeliveryResult result, bool focus_lost = false);
|
Outcome finish(DeliveryResult result, bool focus_lost = false);
|
||||||
void release_if_idle();
|
void release_if_idle();
|
||||||
|
void trace(const std::string& line) const;
|
||||||
DeliveryFactory acquire_;
|
DeliveryFactory acquire_;
|
||||||
Clock clock_;
|
Clock clock_;
|
||||||
std::function<void()> release_idle_;
|
std::function<void()> release_idle_;
|
||||||
Guard permitted_;
|
Guard permitted_;
|
||||||
|
Trace trace_;
|
||||||
std::unique_ptr<Task> task_;
|
std::unique_ptr<Task> task_;
|
||||||
std::optional<std::chrono::steady_clock::time_point> last_commit_;
|
std::optional<std::chrono::steady_clock::time_point> last_commit_;
|
||||||
bool retained_ = false;
|
bool retained_ = false;
|
||||||
|
|||||||
+46
-3
@@ -17,6 +17,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
namespace frameyap {
|
namespace frameyap {
|
||||||
namespace {
|
namespace {
|
||||||
@@ -64,12 +65,50 @@ private:
|
|||||||
Worker worker_;
|
Worker worker_;
|
||||||
std::string backend_, model_, manifest_, root_;
|
std::string backend_, model_, manifest_, root_;
|
||||||
};
|
};
|
||||||
|
// Opt-in (Advanced debug) native delivery diagnostics. Metadata only: never
|
||||||
|
// transcript text. Owner-private file next to worker-debug.log.
|
||||||
|
class DeliveryTrace {
|
||||||
|
public:
|
||||||
|
~DeliveryTrace() { set_enabled(false); }
|
||||||
|
void set_enabled(bool enabled) {
|
||||||
|
if (enabled == enabled_) return;
|
||||||
|
enabled_ = enabled;
|
||||||
|
if (fd_ >= 0) { ::close(fd_); fd_ = -1; }
|
||||||
|
if (enabled_) {
|
||||||
|
try { fd_ = open_private_debug_log("delivery-debug.log", "delivery-debug.previous.log"); }
|
||||||
|
catch (...) { fd_ = -1; }
|
||||||
|
written_ = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void operator()(const std::string& line) {
|
||||||
|
if (fd_ < 0 || written_ > 1024 * 1024) return;
|
||||||
|
const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
std::chrono::steady_clock::now() - start_).count();
|
||||||
|
const auto out = std::to_string(ms) + "ms " + line + "\n";
|
||||||
|
if (::write(fd_, out.data(), out.size()) > 0) written_ += out.size();
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
bool enabled_ = false;
|
||||||
|
int fd_ = -1;
|
||||||
|
size_t written_ = 0;
|
||||||
|
std::chrono::steady_clock::time_point start_ = std::chrono::steady_clock::now();
|
||||||
|
};
|
||||||
class NativeFocus final : public ControllerFocus {
|
class NativeFocus final : public ControllerFocus {
|
||||||
public:
|
public:
|
||||||
bool arm() override { return guard_.arm(); }
|
explicit NativeFocus(DeliveryTrace& trace) : trace_(trace) {}
|
||||||
bool valid() override { return guard_.valid(); }
|
bool arm() override {
|
||||||
|
if (guard_.arm()) { trace_("focus armed"); return true; }
|
||||||
|
trace_(std::string("focus arm failed: ") + guard_.failure());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool valid() override {
|
||||||
|
if (guard_.valid()) return true;
|
||||||
|
trace_(std::string("focus invalid: ") + guard_.failure());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
private:
|
private:
|
||||||
FocusGuard guard_;
|
FocusGuard guard_;
|
||||||
|
DeliveryTrace& trace_;
|
||||||
};
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
int run(const Options& options) {
|
int run(const Options& options) {
|
||||||
@@ -85,6 +124,8 @@ int run(const Options& options) {
|
|||||||
auto old_term = std::signal(SIGTERM, signal_stop);
|
auto old_term = std::signal(SIGTERM, signal_stop);
|
||||||
struct Restore { decltype(old_int) a, b; ~Restore() { std::signal(SIGINT, a); std::signal(SIGTERM, b); } } restore{old_int, old_term};
|
struct Restore { decltype(old_int) a, b; ~Restore() { std::signal(SIGINT, a); std::signal(SIGTERM, b); } } restore{old_int, old_term};
|
||||||
Overlay overlay(options.assets, options.font, options.mount);
|
Overlay overlay(options.assets, options.font, options.mount);
|
||||||
|
DeliveryTrace trace;
|
||||||
|
trace.set_enabled(overlay.advanced_debug());
|
||||||
NativeWorker worker(options);
|
NativeWorker worker(options);
|
||||||
NativeAudio audio;
|
NativeAudio audio;
|
||||||
const DeliveryFactory acquire = [&]() -> std::unique_ptr<DeliveryLease> {
|
const DeliveryFactory acquire = [&]() -> std::unique_ptr<DeliveryLease> {
|
||||||
@@ -94,7 +135,8 @@ int run(const Options& options) {
|
|||||||
};
|
};
|
||||||
PacedDelivery paced(acquire, std::chrono::steady_clock::now,
|
PacedDelivery paced(acquire, std::chrono::steady_clock::now,
|
||||||
[] { TextInput::release_idle(); }, [] { return !interrupted; });
|
[] { TextInput::release_idle(); }, [] { return !interrupted; });
|
||||||
Controller controller(audio, worker, acquire, [] { return std::make_unique<NativeFocus>(); },
|
paced.set_trace([&trace](const std::string& line) { trace(line); });
|
||||||
|
Controller controller(audio, worker, acquire, [&trace] { return std::make_unique<NativeFocus>(trace); },
|
||||||
overlay.quick_inputs(), overlay.auto_insert(), overlay.advanced_debug(),
|
overlay.quick_inputs(), overlay.auto_insert(), overlay.advanced_debug(),
|
||||||
overlay.close_mic_when_idle(), &paced);
|
overlay.close_mic_when_idle(), &paced);
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
@@ -190,6 +232,7 @@ int run(const Options& options) {
|
|||||||
"Selected model unavailable or being checked. Recording disabled until offline verification.");
|
"Selected model unavailable or being checked. Recording disabled until offline verification.");
|
||||||
}
|
}
|
||||||
bool debug_changed = overlay.advanced_debug() != previous_debug;
|
bool debug_changed = overlay.advanced_debug() != previous_debug;
|
||||||
|
trace.set_enabled(overlay.advanced_debug());
|
||||||
controller.settings(overlay.auto_insert(), overlay.advanced_debug(), overlay.close_mic_when_idle());
|
controller.settings(overlay.auto_insert(), overlay.advanced_debug(), overlay.close_mic_when_idle());
|
||||||
// Debug consent and model actions cancel work before any stale input.
|
// Debug consent and model actions cancel work before any stale input.
|
||||||
if (debug_changed || !model_actions.empty()) {
|
if (debug_changed || !model_actions.empty()) {
|
||||||
|
|||||||
+17
-16
@@ -138,21 +138,6 @@ void check_log_target(int dir, const char* name) {
|
|||||||
::close(fd);
|
::close(fd);
|
||||||
if (!valid) throw std::runtime_error("unsafe existing worker debug log");
|
if (!valid) throw std::runtime_error("unsafe existing worker debug log");
|
||||||
}
|
}
|
||||||
int create_debug_log() {
|
|
||||||
int dir = state_directory();
|
|
||||||
try {
|
|
||||||
check_log_target(dir, "worker-debug.log");
|
|
||||||
check_log_target(dir, "worker-debug.previous.log");
|
|
||||||
if (::unlinkat(dir, "worker-debug.previous.log", 0) && errno != ENOENT)
|
|
||||||
throw std::runtime_error("cannot rotate worker debug log");
|
|
||||||
if (::renameat(dir, "worker-debug.log", dir, "worker-debug.previous.log") && errno != ENOENT)
|
|
||||||
throw std::runtime_error("cannot rotate worker debug log");
|
|
||||||
int fd = ::openat(dir, "worker-debug.log", O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC, 0600);
|
|
||||||
if (fd < 0) throw std::runtime_error("cannot create private worker debug log");
|
|
||||||
::close(dir);
|
|
||||||
return fd;
|
|
||||||
} catch (...) { ::close(dir); throw; }
|
|
||||||
}
|
|
||||||
void log_bytes(int fd, const unsigned char* data, size_t size) {
|
void log_bytes(int fd, const unsigned char* data, size_t size) {
|
||||||
while (size) {
|
while (size) {
|
||||||
ssize_t n = ::write(fd, data, size);
|
ssize_t n = ::write(fd, data, size);
|
||||||
@@ -194,6 +179,22 @@ void write_all(int fd, const unsigned char* data, size_t size) {
|
|||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
int open_private_debug_log(const char* name, const char* previous) {
|
||||||
|
int dir = state_directory();
|
||||||
|
try {
|
||||||
|
check_log_target(dir, name);
|
||||||
|
check_log_target(dir, previous);
|
||||||
|
if (::unlinkat(dir, previous, 0) && errno != ENOENT)
|
||||||
|
throw std::runtime_error("cannot rotate debug log");
|
||||||
|
if (::renameat(dir, name, dir, previous) && errno != ENOENT)
|
||||||
|
throw std::runtime_error("cannot rotate debug log");
|
||||||
|
int fd = ::openat(dir, name, O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC, 0600);
|
||||||
|
if (fd < 0) throw std::runtime_error("cannot create private debug log");
|
||||||
|
::close(dir);
|
||||||
|
return fd;
|
||||||
|
} catch (...) { ::close(dir); throw; }
|
||||||
|
}
|
||||||
|
|
||||||
struct Worker::State {
|
struct Worker::State {
|
||||||
pid_t pid = -1;
|
pid_t pid = -1;
|
||||||
int to_child = -1, from_child = -1, debug_pipe = -1, debug_file = -1;
|
int to_child = -1, from_child = -1, debug_pipe = -1, debug_file = -1;
|
||||||
@@ -293,7 +294,7 @@ void Worker::start(const std::string& python, const std::string& script,
|
|||||||
if (!::mkdtemp(tmp.data())) throw std::runtime_error("cannot create private clip directory");
|
if (!::mkdtemp(tmp.data())) throw std::runtime_error("cannot create private clip directory");
|
||||||
state_->dir = tmp.data();
|
state_->dir = tmp.data();
|
||||||
::chmod(state_->dir.c_str(), 0700);
|
::chmod(state_->dir.c_str(), 0700);
|
||||||
if (advanced_debug) state_->debug_file = create_debug_log();
|
if (advanced_debug) state_->debug_file = open_private_debug_log("worker-debug.log", "worker-debug.previous.log");
|
||||||
int in[2]{-1,-1}, out[2]{-1,-1}, debug[2]{-1,-1};
|
int in[2]{-1,-1}, out[2]{-1,-1}, debug[2]{-1,-1};
|
||||||
if (::pipe2(in, O_CLOEXEC) || ::pipe2(out, O_CLOEXEC) ||
|
if (::pipe2(in, O_CLOEXEC) || ::pipe2(out, O_CLOEXEC) ||
|
||||||
(advanced_debug && ::pipe2(debug, O_CLOEXEC))) {
|
(advanced_debug && ::pipe2(debug, O_CLOEXEC))) {
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ struct WorkerReply {
|
|||||||
std::string error;
|
std::string error;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Opens (rotating `previous`) an owner-private 0600 file in the FrameYap state
|
||||||
|
// directory, or throws. Used only for opt-in advanced debugging.
|
||||||
|
int open_private_debug_log(const char* name, const char* previous);
|
||||||
|
|
||||||
class Worker {
|
class Worker {
|
||||||
public:
|
public:
|
||||||
explicit Worker(std::chrono::milliseconds warmup_timeout = std::chrono::seconds(120),
|
explicit Worker(std::chrono::milliseconds warmup_timeout = std::chrono::seconds(120),
|
||||||
|
|||||||
Reference in new issue
Block a user