Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 96 additions & 15 deletions src/metkit/mars2grib/CoreOperations.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@
#include <string>
#include <tuple>
#include <utility>
#include <cstdio>
#include <unistd.h>

#if defined(__APPLE__) && defined(__MACH__)
#include <pthread.h>
#elif defined(__linux__)
#include <sys/syscall.h>
#endif


/// Project includes
/// @note: clang-format needs to be off here to preserve the logical grouping of
Expand All @@ -47,6 +56,52 @@
namespace metkit::mars2grib {


namespace detail {

/// @brief Generates a strictly formatted log filename safely (Linux/macOS only).
/// Guarantees no exceptions will escape and crash the simulation.
inline std::string generateEncodingLogFilename() noexcept {
try {
// 1. Get true Process ID
pid_t pid = getpid();

// 2. Get true OS Thread ID
unsigned long long tid = 0;

#if defined(__APPLE__) && defined(__MACH__)
uint64_t mac_tid;
pthread_threadid_np(NULL, &mac_tid);
tid = static_cast<unsigned long long>(mac_tid);
#elif defined(__linux__)
// SYS_gettid is universally supported on Linux, even on old glibc versions
tid = static_cast<unsigned long long>(syscall(SYS_gettid));
#else
// Ultimate fallback if compiled on an unknown UNIX variant
tid = 0;
#endif

// 3. Format the string: encodingStack_%06d_%06llu.txt
// %06d ensures at least 6 digits with leading zeros (equivalent to %6.6d)
char buffer[64];
int written = std::snprintf(buffer, sizeof(buffer),
"encodingStack_%06d_%06llu.txt",
static_cast<int>(pid), tid);

if (written > 0 && static_cast<size_t>(written) < sizeof(buffer)) {
return std::string(buffer);
}

return "encodingStack_fallback.txt";

} catch (...) {
// Catches std::bad_alloc if std::string creation fails due to OOM
return "encodingStack_fallback.txt";
}
}

} // namespace detail


///
/// @brief Internal engine providing atomic encoding and diagnostic services.
///
Expand Down Expand Up @@ -86,10 +141,10 @@ struct CoreOperations {

return {activeMars, activePar};
}
catch (const std::exception& e) {
catch (...) {
// Wrap any exception in a CoreOperations-specific exception to provide context
std::throw_with_nested(
mars2grib::utils::exceptions::Mars2GribGenericException("Error during metadata normalization", Here()));
mars2grib::utils::exceptions::Mars2GribCoreException("Error during metadata normalization", Here()));
}
}

Expand Down Expand Up @@ -118,10 +173,10 @@ struct CoreOperations {
return SpecializedEncoder<MarsDict_t, ParDict_t, OptDict_t, OutDict_t>{std::move(layout)}.encode(mars, misc,
opt);
}
catch (const std::exception& e) {
catch (...) {
// Wrap any exception in a CoreOperations-specific exception to provide context
std::throw_with_nested(
mars2grib::utils::exceptions::Mars2GribGenericException("Error during header encoding", Here()));
mars2grib::utils::exceptions::Mars2GribCoreException("Error during header encoding", Here()));
}
}

Expand All @@ -143,10 +198,10 @@ struct CoreOperations {
metkit::mars2grib::backend::encodeValues(values, misc, opt, *handle);
return handle;
}
catch (const std::exception& e) {
catch (...) {
// Wrap any exception in a CoreOperations-specific exception to provide context
std::throw_with_nested(
mars2grib::utils::exceptions::Mars2GribGenericException("Error during value encoding", Here()));
mars2grib::utils::exceptions::Mars2GribCoreException("Error during value encoding", Here()));
}
}

Expand Down Expand Up @@ -251,14 +306,10 @@ struct CoreOperations {
// 4. Inject Values
return encodeValues(values, activeMisc, options, std::move(gribHeader));
}
catch (const std::exception& e) {
printExtendedStack(e);
throw;
}
catch (...) {
// Fallback for non-standard exceptions
throw metkit::mars2grib::utils::exceptions::Mars2GribGenericException("Unknown error during encoding",
Here());
// Wrap any exception in a CoreOperations-specific exception to provide context
std::throw_with_nested(
mars2grib::utils::exceptions::Mars2GribCoreException("Error during encoding", Here()));
}
}

Expand All @@ -281,12 +332,42 @@ struct CoreOperations {

return debug_convert_GribHeaderLayoutData_to_json(layout);
}
catch (const std::exception& e) {
catch (...) {
// Wrap any exception in a CoreOperations-specific exception to provide context
std::throw_with_nested(
mars2grib::utils::exceptions::Mars2GribGenericException("Error during header test dump", Here()));
mars2grib::utils::exceptions::Mars2GribCoreException("Error during header test dump", Here()));
}
}

static std::string generateStack( const std::exception& e, const std::string& msg, const eckit::CodeLocation& loc ){

using metkit::mars2grib::utils::exceptions::printExtendedStack;
using metkit::mars2grib::utils::exceptions::lineSize;

const std::string logName = detail::generateEncodingLogFilename();

Comment on lines +342 to +348
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes introduce new observable debugging behavior (per-thread/per-process stack log files and a potential reproducer path), but src/metkit/mars2grib/docs/doxygen/* currently doesn’t describe this API contract. Please update the mars2grib docs to mention: where the stack files are written, naming scheme, and how users should find/consume the reproducer output.

Copilot uses AI. Check for mistakes.
std::ofstream out(logName);
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generateStack() opens the output file but doesn’t check whether the stream is actually open/healthy before writing. If the open fails (permissions/CWD not writable), the diagnostics will be silently lost; add an if (!out) handling path (and decide whether to throw or return an empty/failed indicator).

Suggested change
std::ofstream out(logName);
std::ofstream out(logName);
if (!out) {
return {};
}

Copilot uses AI. Check for mistakes.
std::size_t level = 0;
std::size_t frame = 1;

out << "+ " << std::string(lineSize, '=') << std::endl
<< "+ frame " << frame << std::endl
<< "+ " << std::string(lineSize, '-') << std::endl;

out << "+ file: " << loc.file() << "\n"
<< "+ function: " << loc.func() << "\n"
<< "+ line: " << loc.line() << "\n"
<< "+ link: " << loc.file() << ":" << loc.line() << "\n"
<< "+ message: " << msg << "\n";

out << "+ " << std::string(lineSize, '+') << std::endl;

printExtendedStack( e, out, ++level, ++frame );

return "test.log";
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generateStack() always returns the constant string "test.log", even though it writes to logName. This makes the returned filename incorrect and prevents the API layer from reporting the actual diagnostics file path. Return logName (or the fallback name) instead.

Suggested change
return "test.log";
return logName;

Copilot uses AI. Check for mistakes.

}

};

} // namespace metkit::mars2grib
132 changes: 108 additions & 24 deletions src/metkit/mars2grib/api/Mars2Grib.cc
Original file line number Diff line number Diff line change
Expand Up @@ -120,61 +120,145 @@ Mars2Grib::Mars2Grib(const eckit::LocalConfiguration& opts) : opts_{readOptions(
std::unique_ptr<metkit::codes::CodesHandle> Mars2Grib::encode(const std::vector<double>& values,
const eckit::LocalConfiguration& mars,
const eckit::LocalConfiguration& misc) {
return CoreOperations::encode<double, eckit::LocalConfiguration, eckit::LocalConfiguration, Options,
metkit::codes::CodesHandle>(Span<const double>{values}, mars, misc, opts_, language_);
try {
return CoreOperations::encode<double, eckit::LocalConfiguration, eckit::LocalConfiguration, Options,
metkit::codes::CodesHandle>(Span<const double>{values}, mars, misc, opts_,
language_);
}
catch (const std::exception& e) {
std::string logFile = CoreOperations::generateStack( e, "Error during API::encode call", Here() );
throw eckit::Exception("Error during encoding", Here());
}
Comment on lines +128 to +131
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The caught exception is logged to a file, but the returned logFile is unused and the rethrown eckit::Exception("Error during encoding") discards the original exception chain. This is inconsistent with the PR description about preserving nested context at the API boundary; consider rethrowing with nesting (or at least include the generated log filename in the API-level message) so callers can correlate failures with the diagnostics file.

Copilot uses AI. Check for mistakes.
catch (...) {
// Fallback for non-standard exceptions
throw eckit::Exception("Unknown error during encoding", Here());
}
}

std::unique_ptr<metkit::codes::CodesHandle> Mars2Grib::encode(const std::vector<float>& values,
const eckit::LocalConfiguration& mars,
const eckit::LocalConfiguration& misc) {
return CoreOperations::encode<float, eckit::LocalConfiguration, eckit::LocalConfiguration, Options,
metkit::codes::CodesHandle>(Span<const float>{values}, mars, misc, opts_, language_);
try {
return CoreOperations::encode<float, eckit::LocalConfiguration, eckit::LocalConfiguration, Options,
metkit::codes::CodesHandle>(Span<const float>{values}, mars, misc, opts_,
language_);
}
catch (const std::exception& e) {
std::string logFile = CoreOperations::generateStack( e, "Error during API::encode call", Here() );
throw eckit::Exception("Error during encoding", Here());
}
catch (...) {
// Fallback for non-standard exceptions
throw eckit::Exception("Unknown error during encoding", Here());
}
}

std::unique_ptr<metkit::codes::CodesHandle> Mars2Grib::encode(const std::vector<double>& values,
const eckit::LocalConfiguration& mars) {
const eckit::LocalConfiguration misc{};
return CoreOperations::encode<double, eckit::LocalConfiguration, eckit::LocalConfiguration, Options,
metkit::codes::CodesHandle>(Span<const double>{values}, mars, misc, opts_, language_);
try {
const eckit::LocalConfiguration misc{};
return CoreOperations::encode<double, eckit::LocalConfiguration, eckit::LocalConfiguration, Options,
metkit::codes::CodesHandle>(Span<const double>{values}, mars, misc, opts_,
language_);
}
catch (const std::exception& e) {
std::string logFile = CoreOperations::generateStack( e, "Error during API::encode call", Here() );
throw eckit::Exception("Error during encoding", Here());
}
catch (...) {
// Fallback for non-standard exceptions
throw eckit::Exception("Unknown error during encoding", Here());
}
}

std::unique_ptr<metkit::codes::CodesHandle> Mars2Grib::encode(const std::vector<float>& values,
const eckit::LocalConfiguration& mars) {
const eckit::LocalConfiguration misc{};
return CoreOperations::encode<float, eckit::LocalConfiguration, eckit::LocalConfiguration, Options,
metkit::codes::CodesHandle>(Span<const float>{values}, mars, misc, opts_, language_);
try {
const eckit::LocalConfiguration misc{};
return CoreOperations::encode<float, eckit::LocalConfiguration, eckit::LocalConfiguration, Options,
metkit::codes::CodesHandle>(Span<const float>{values}, mars, misc, opts_,
language_);
}
catch (const std::exception& e) {
std::string logFile = CoreOperations::generateStack( e, "Error during API::encode call", Here() );
throw eckit::Exception("Error during encoding", Here());
}
catch (...) {
// Fallback for non-standard exceptions
throw eckit::Exception("Unknown error during encoding", Here());
}
}

std::unique_ptr<metkit::codes::CodesHandle> Mars2Grib::encode(const double* values, size_t length,
const eckit::LocalConfiguration& mars,
const eckit::LocalConfiguration& misc) {
return CoreOperations::encode<double, eckit::LocalConfiguration, eckit::LocalConfiguration, Options,
metkit::codes::CodesHandle>(Span<const double>{values, length}, mars, misc, opts_,
language_);
try {
return CoreOperations::encode<double, eckit::LocalConfiguration, eckit::LocalConfiguration, Options,
metkit::codes::CodesHandle>(Span<const double>{values, length}, mars, misc, opts_,
language_);
}
catch (const std::exception& e) {
std::string logFile = CoreOperations::generateStack( e, "Error during API::encode call", Here() );
throw eckit::Exception("Error during encoding", Here());
}
catch (...) {
// Fallback for non-standard exceptions
throw eckit::Exception("Unknown error during encoding", Here());
}
}

std::unique_ptr<metkit::codes::CodesHandle> Mars2Grib::encode(const float* values, size_t length,
const eckit::LocalConfiguration& mars,
const eckit::LocalConfiguration& misc) {
return CoreOperations::encode<float, eckit::LocalConfiguration, eckit::LocalConfiguration, Options,
metkit::codes::CodesHandle>(Span<const float>{values, length}, mars, misc, opts_,
language_);
try {
return CoreOperations::encode<float, eckit::LocalConfiguration, eckit::LocalConfiguration, Options,
metkit::codes::CodesHandle>(Span<const float>{values, length}, mars, misc, opts_,
language_);
}
catch (const std::exception& e) {
std::string logFile = CoreOperations::generateStack( e, "Error during API::encode call", Here() );
throw eckit::Exception("Error during encoding", Here());
}
catch (...) {
// Fallback for non-standard exceptions
throw eckit::Exception("Unknown error during encoding", Here());
}
}

std::unique_ptr<metkit::codes::CodesHandle> Mars2Grib::encode(const double* values, size_t length,
const eckit::LocalConfiguration& mars) {
const eckit::LocalConfiguration misc{};
return CoreOperations::encode<double, eckit::LocalConfiguration, eckit::LocalConfiguration, Options,
metkit::codes::CodesHandle>(Span<const double>{values, length}, mars, misc, opts_,
language_);
try {
const eckit::LocalConfiguration misc{};
return CoreOperations::encode<double, eckit::LocalConfiguration, eckit::LocalConfiguration, Options,
metkit::codes::CodesHandle>(Span<const double>{values, length}, mars, misc, opts_,
language_);
}
catch (const std::exception& e) {
std::string logFile = CoreOperations::generateStack( e, "Error during API::encode call", Here() );
throw eckit::Exception("Error during encoding", Here());
}
catch (...) {
// Fallback for non-standard exceptions
throw eckit::Exception("Unknown error during encoding", Here());
}
}

std::unique_ptr<metkit::codes::CodesHandle> Mars2Grib::encode(const float* values, size_t length,
const eckit::LocalConfiguration& mars) {
const eckit::LocalConfiguration misc{};
return CoreOperations::encode<float, eckit::LocalConfiguration, eckit::LocalConfiguration, Options,
metkit::codes::CodesHandle>(Span<const float>{values, length}, mars, misc, opts_,
language_);
try {
const eckit::LocalConfiguration misc{};
return CoreOperations::encode<float, eckit::LocalConfiguration, eckit::LocalConfiguration, Options,
metkit::codes::CodesHandle>(Span<const float>{values, length}, mars, misc, opts_,
language_);
}
catch (const std::exception& e) {
std::string logFile = CoreOperations::generateStack( e, "Error during API::encode call", Here() );
throw eckit::Exception("Error during encoding", Here());
}
catch (...) {
// Fallback for non-standard exceptions
throw eckit::Exception("Unknown error during encoding", Here());
}
}

} // namespace metkit::mars2grib
42 changes: 42 additions & 0 deletions src/metkit/mars2grib/debug/MockupDictionary.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#include <memory>
#include <vector>

#include "metkit/mars2grib/debug/reproducer.h"
#include "metkit/codes/api/CodesTypes.h"
Comment on lines +1 to +5
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This header is missing an include guard/#pragma once, which can lead to ODR/multiple-include issues. Also, it uses std::string but doesn’t include <string> directly (currently relies on transitive includes). Add #pragma once and include <string> explicitly.

Copilot uses AI. Check for mistakes.

// ============================================================================
// MockDictionary Definition
// ============================================================================
namespace metkit::mars2grib::debug::reproducer {


class MockDictionary {
public:
std::shared_ptr<Recorder> recorder;

// Pipeline constructor
explicit MockDictionary(std::shared_ptr<Recorder> rec)
: recorder(std::move(rec)) {}

// Output utility
void dump(const std::string& filename) const {
if (recorder) recorder->generate(filename);
}

template <typename T>
void set(const std::string& k, const T& v) {
recorder->record(k, v);
}

template <typename T>
void set_array(const std::string& k, const std::vector<T>& v) {
recorder->record_array(k, v.data(), v.size());
}

template <typename T>
void set_array(const std::string& k, const metkit::codes::Span<T>& v) {
recorder->record_array(k, v.data(), v.size());
}
};

} // namespace metkit::mars2grib::debug::reproducer
Loading
Loading