Clear the open minors: lost marking and overrun logging, form teardown guard, line breaks and ERANGE in config, probe strength

The E-1608 source marks every restart as lost and logs each overrun; its form stops touching
siblings after the chooser destroys them; the link skips command id 0, retries a full-buffer
discover send and ignores a zero-length scan read. Session files escape \n and \r; get_int and
get_double reject out-of-range values; the UDPS port spin reports changes; a skipped source panel
says why on stderr. The E-1608 probe now checks the signal names and the reconnect itself.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino FerrariandClaude Opus 4.6 committed 2026-11-02 00:19:27 +01:00
1 parent 6245811699
commit 87822ed4f5
9 files changed
+95 -18

No files matched your search

+12
View File
@@ -81,6 +81,7 @@ struct Form {
GtkWidget *dir[8], *do_init, *plot_counter, *ao_init[2];
ChangeCb changed = std::make_shared<std::function<void()>>();
bool updating = false;
bool alive = true; ///< cleared when the chooser destroys the rows; handlers then touch nothing
bool diff() const { return sel_of(mode) == 1; }
int enabled_ai() const {
@@ -170,17 +171,27 @@ using FormPtr = std::shared_ptr<Form>;
void on_notify(GObject*, GParamSpec*, gpointer user) {
Form& f = **static_cast<FormPtr*>(user);
if (!f.alive) return;
f.refresh();
if (!f.updating && f.changed && *f.changed) (*f.changed)();
}
void on_edited(GtkEditable*, gpointer user) {
Form& f = **static_cast<FormPtr*>(user);
if (!f.alive) return;
if (!f.updating && f.changed && *f.changed) (*f.changed)();
}
void free_form(gpointer d, GClosure*) { delete static_cast<FormPtr*>(d); }
/// "destroy" on a row group: the siblings the handlers reach by raw pointer may already be gone.
void on_form_destroy(GtkWidget*, gpointer user) { (**static_cast<FormPtr*>(user)).alive = false; }
void hook_destroy(GtkWidget* w, const FormPtr& f) {
g_signal_connect_data(w, "destroy", G_CALLBACK(on_form_destroy), new FormPtr(f), free_form,
static_cast<GConnectFlags>(0));
}
void hook_notify(GtkWidget* w, const char* signal, const FormPtr& f) {
g_signal_connect_data(w, signal, G_CALLBACK(on_notify), new FormPtr(f), free_form,
static_cast<GConnectFlags>(0));
@@ -254,6 +265,7 @@ class E1608Kind final : public SourceKind {
f->ai_group = ai;
adw_expander_row_set_expanded(ADW_EXPANDER_ROW(conn), TRUE);
for (GtkWidget* w : {conn, acq, ai, dio}) hook_destroy(w, f);
for (GtkWidget* w : {f->address, f->mac, f->subnet}) hook_entry(w, f);
for (GtkWidget* w : {f->port, f->code, f->timeout, f->rate, f->poll_rate, f->do_init,
f->ao_init[0], f->ao_init[1]})
+13 -4
View File
@@ -199,9 +199,16 @@ std::vector<DeviceInfo> discover(const std::vector<std::string>& targets, int po
} catch (const LinkError&) {
continue;
}
// A sweep may hit unreachable hosts; those sends fail and are simply skipped.
(void)::sendto(u.get(), &probe, 1, MSG_NOSIGNAL, reinterpret_cast<const sockaddr*>(&a),
sizeof a);
// A sweep may hit unreachable hosts; those sends fail and are simply skipped. A full send
// buffer is not that: wait for room once and retry, so a large sweep does not lose probes.
const auto send_probe = [&] {
return ::sendto(u.get(), &probe, 1, MSG_NOSIGNAL, reinterpret_cast<const sockaddr*>(&a),
sizeof a);
};
if (send_probe() < 0 && (errno == EAGAIN || errno == ENOBUFS)) {
wait_fd(u.get(), POLLOUT, 20);
(void)send_probe();
}
}
const auto deadline = Clock::now() + std::chrono::milliseconds(wait_ms);
for (;;) {
@@ -288,7 +295,8 @@ std::vector<std::uint8_t> Link::command(std::uint8_t cmd, const std::vector<std:
while (::recv(cmd_.get(), junk, sizeof junk, MSG_DONTWAIT) > 0) {
}
}
const std::uint8_t id = ++next_id_;
if (++next_id_ == 0) next_id_ = 1; // id 0 is never used, in case a board treats it specially
const std::uint8_t id = next_id_;
std::vector<std::uint8_t> f;
f.reserve(7 + payload.size());
f.push_back(0xDB);
@@ -349,6 +357,7 @@ void Link::send_filler() {
std::size_t Link::read_scan(std::uint8_t* buf, std::size_t cap, int timeout_ms) {
if (!scan_.valid()) throw LinkError(Kind::Io, "the scan socket is not open");
if (cap == 0) return 0; // recv would return 0 and read as a close
if (!wait_fd(scan_.get(), POLLIN, timeout_ms)) return 0;
const ssize_t n = ::recv(scan_.get(), buf, cap, MSG_DONTWAIT);
if (n > 0) return static_cast<std::size_t>(n);
+2
View File
@@ -430,10 +430,12 @@ void E1608Source::check_status() {
sink_->on_event(SourceEvent::Error, "scan overrun; restarting");
restart_scan();
pending_lost_ = 1;
last_logged_.clear(); // a later overrun on this connection is news again, not a retry echo
} else if (!(st & 2) && cfg_.auto_restart && cfg_.trigger == Trigger::None) {
// With a hardware trigger the board may report "not running" while it waits for the edge;
// restarting then would reset the wait forever, so only the overrun bit restarts the scan.
restart_scan();
pending_lost_ = 1; // the samples between the stop and the restart are gone either way
}
}
+6 -2
View File
@@ -5,6 +5,8 @@
#include <algorithm>
#include <cctype>
#include <cerrno>
#include <cmath>
#include <cstdlib>
namespace udpscope {
@@ -35,16 +37,18 @@ long SourceConfig::get_int(const std::string& key, long def) const {
if (!has(key)) return def;
const std::string v = get(key);
char* end = nullptr;
errno = 0;
const long n = std::strtol(v.c_str(), &end, 10);
return (end == v.c_str() || *end != '\0') ? def : n;
return (end == v.c_str() || *end != '\0' || errno == ERANGE) ? def : n;
}
double SourceConfig::get_double(const std::string& key, double def) const {
if (!has(key)) return def;
const std::string v = get(key);
char* end = nullptr;
errno = 0;
const double d = std::strtod(v.c_str(), &end);
return (end == v.c_str() || *end != '\0') ? def : d;
return (end == v.c_str() || *end != '\0' || std::isinf(d)) ? def : d;
}
bool SourceConfig::get_bool(const std::string& key, bool def) const {
+12
View File
@@ -25,6 +25,17 @@ void on_edited(GtkEditable*, gpointer user) {
if (cb && *cb) (*cb)();
}
void on_value(GObject*, GParamSpec*, gpointer user) {
const auto& cb = *static_cast<ChangeCb*>(user);
if (cb && *cb) (*cb)();
}
void hook_value(GtkWidget* spin, const ChangeCb& cb) {
g_signal_connect_data(spin, "notify::value", G_CALLBACK(on_value), new ChangeCb(cb),
+[](gpointer d, GClosure*) { delete static_cast<ChangeCb*>(d); },
static_cast<GConnectFlags>(0));
}
void hook(GtkWidget* entry, const ChangeCb& cb) {
g_signal_connect_data(entry, "changed", G_CALLBACK(on_edited), new ChangeCb(cb),
+[](gpointer d, GClosure*) { delete static_cast<ChangeCb*>(d); },
@@ -58,6 +69,7 @@ class UdpsKind final : public SourceKind {
ChangeCb cb = std::make_shared<std::function<void()>>();
hook(server, cb);
hook_value(port, cb);
hook(group, cb);
hook(iface, cb);