f2515b7ce2
Readers (ndiout, fakesink) now handle MXL_ERR_FLOW_INVALID by releasing and recreating the flow reader, then realigning the index to current time. This lets consumer nodes survive a producer restart without exiting. All mxlCreateInstance/FlowWriter/FlowReader options args changed from nullptr to "" to match MXL reference implementation style. mxlReleaseFlowReader calls guarded with null checks so cleanup is safe when a mid-run reconnect attempt fails. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
127 lines
4.5 KiB
C++
127 lines
4.5 KiB
C++
#pragma once
|
|
|
|
#include <cstdarg>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <string>
|
|
#include <nlohmann/json.hpp>
|
|
#include <mxl/mxl.h>
|
|
#include "Signal.hpp"
|
|
|
|
namespace dmf {
|
|
|
|
inline const char* mxl_status_str(mxlStatus s) noexcept {
|
|
switch (s) {
|
|
case MXL_STATUS_OK: return "MXL_STATUS_OK";
|
|
case MXL_ERR_UNKNOWN: return "MXL_ERR_UNKNOWN";
|
|
case MXL_ERR_FLOW_NOT_FOUND: return "MXL_ERR_FLOW_NOT_FOUND";
|
|
case MXL_ERR_OUT_OF_RANGE_TOO_LATE: return "MXL_ERR_OUT_OF_RANGE_TOO_LATE";
|
|
case MXL_ERR_OUT_OF_RANGE_TOO_EARLY: return "MXL_ERR_OUT_OF_RANGE_TOO_EARLY";
|
|
case MXL_ERR_INVALID_FLOW_READER: return "MXL_ERR_INVALID_FLOW_READER";
|
|
case MXL_ERR_INVALID_FLOW_WRITER: return "MXL_ERR_INVALID_FLOW_WRITER";
|
|
case MXL_ERR_TIMEOUT: return "MXL_ERR_TIMEOUT";
|
|
case MXL_ERR_INVALID_ARG: return "MXL_ERR_INVALID_ARG";
|
|
case MXL_ERR_CONFLICT: return "MXL_ERR_CONFLICT";
|
|
case MXL_ERR_PERMISSION_DENIED: return "MXL_ERR_PERMISSION_DENIED";
|
|
case MXL_ERR_FLOW_INVALID: return "MXL_ERR_FLOW_INVALID";
|
|
case MXL_ERR_STRLEN: return "MXL_ERR_STRLEN";
|
|
case MXL_ERR_INTERRUPTED: return "MXL_ERR_INTERRUPTED";
|
|
case MXL_ERR_NO_FABRIC: return "MXL_ERR_NO_FABRIC";
|
|
case MXL_ERR_INVALID_STATE: return "MXL_ERR_INVALID_STATE";
|
|
case MXL_ERR_INTERNAL: return "MXL_ERR_INTERNAL";
|
|
case MXL_ERR_NOT_READY: return "MXL_ERR_NOT_READY";
|
|
case MXL_ERR_NOT_FOUND: return "MXL_ERR_NOT_FOUND";
|
|
case MXL_ERR_EXISTS: return "MXL_ERR_EXISTS";
|
|
case MXL_ERR_UNSUPPORTED_OPERATION: return "MXL_ERR_UNSUPPORTED_OPERATION";
|
|
default: return "MXL_ERR_UNRECOGNIZED";
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Base class for all DMF node binaries.
|
|
//
|
|
// Handles the boilerplate every node needs:
|
|
// - NODE_CONFIG env var → parsed JSON config
|
|
// - MXL_DOMAIN env var → domain path
|
|
// - mxlCreateInstance / mxlDestroyInstance lifecycle
|
|
// - Signal handler installation
|
|
// - [node_id]-prefixed logging
|
|
//
|
|
// Derived class implements only run(), which receives a valid MXL instance
|
|
// and runs until dmf::g_running is false (set by SIGTERM/SIGINT).
|
|
class NodeBase {
|
|
public:
|
|
virtual ~NodeBase() = default;
|
|
|
|
// Entry point for main(). Returns 0 on success, 1 on error.
|
|
int execute() {
|
|
install_signal_handlers();
|
|
|
|
const char* config_env = std::getenv("NODE_CONFIG");
|
|
if (!config_env) {
|
|
fprintf(stderr, "[node] NODE_CONFIG not set\n");
|
|
return 1;
|
|
}
|
|
cfg_ = nlohmann::json::parse(config_env, nullptr, /*allow_exceptions=*/false);
|
|
if (cfg_.is_discarded()) {
|
|
fprintf(stderr, "[node] NODE_CONFIG is not valid JSON\n");
|
|
return 1;
|
|
}
|
|
|
|
node_id_ = cfg_.value("node_id", std::string("node"));
|
|
|
|
const char* domain_env = std::getenv("MXL_DOMAIN");
|
|
#ifdef __APPLE__
|
|
domain_ = domain_env ? domain_env : "/tmp/dmf-studio";
|
|
#else
|
|
domain_ = domain_env ? domain_env : "/dev/shm/dmf-studio";
|
|
#endif
|
|
|
|
log("domain=%s", domain_.c_str());
|
|
|
|
inst_ = mxlCreateInstance(domain_.c_str(), "");
|
|
if (!inst_) {
|
|
log("mxlCreateInstance failed at %s", domain_.c_str());
|
|
return 1;
|
|
}
|
|
|
|
run();
|
|
|
|
mxlDestroyInstance(inst_);
|
|
inst_ = nullptr;
|
|
return 0;
|
|
}
|
|
|
|
protected:
|
|
// Implement the node's processing loop here.
|
|
// Create MXL writers/readers, loop while dmf::g_running, release them before returning.
|
|
virtual void run() = 0;
|
|
|
|
const nlohmann::json& config() const { return cfg_; }
|
|
mxlInstance instance() const { return inst_; }
|
|
const std::string& node_id() const { return node_id_; }
|
|
const std::string& domain() const { return domain_; }
|
|
|
|
// Printf-style log with automatic "[node_id] " prefix and trailing newline.
|
|
#if defined(__GNUC__) || defined(__clang__)
|
|
__attribute__((format(printf, 2, 3)))
|
|
#endif
|
|
void log(const char* fmt, ...) const {
|
|
fprintf(stderr, "[%s] ", node_id_.c_str());
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
vfprintf(stderr, fmt, args);
|
|
va_end(args);
|
|
fputc('\n', stderr);
|
|
}
|
|
|
|
private:
|
|
nlohmann::json cfg_;
|
|
mxlInstance inst_{nullptr};
|
|
std::string node_id_{"node"};
|
|
std::string domain_;
|
|
};
|
|
|
|
} // namespace dmf
|