Skip to content

Conversation

@ivoanjo
Copy link
Member

@ivoanjo ivoanjo commented Nov 11, 2025

What does this PR do?

This PR updates the wrap_with_ffi_result and wrap_with_void_ffi_result macros to catch any panics that happen inside them, returning them as errors.

The error handling is made in such a way (see handle_panic_error for details) that it should be able to report back an error even if we fail to do any allocations.

Important note: Because only the macros have been changed, and ffi APIs that don't use the macros are of course not affected and can still trigger panics. If we like this approach, I'll follow-up with a separate PR to update other APIs to use the new macros.

Motivation

In https://docs.google.com/document/d/1weMu9P03KKhPQ-gh9BMqRrEzpa1BnnY0LaSRGJbfc7A/edit?usp=sharing (Datadog-only link, sorry!) we saw ddog_prof_Exporter_send crashing due to what can be summed up as

ddog_prof_Exporter_send (report a profile) ->
  hyper-util tries to do dns resolution in a separate thread pool ->
    tokio failed to create a new thread ->
      panic and we tear down the app because we can't report a profile

This is not good at all, and this PR solves this inspired by earlier work in #815 and #1083.

Additional Notes

While I don't predict that will happen very often, callers that want to opt-out of the catch unwind behavior can still use the ..._no_catch variants of the macros.

The return type change in ddog_crasht_CrashInfoBuilder_build does change the tag enum entries, which unfortunately is a breaking change.

Ideas on how to work around this? This makes the following enum entries change:

  • DDOG_CRASHT_CRASH_INFO_NEW_RESULT_OK => DDOG_CRASHT_RESULT_HANDLE_CRASH_INFO_OK_HANDLE_CRASH_INFO
  • DDOG_CRASHT_CRASH_INFO_NEW_RESULT_ERR => DDOG_CRASHT_RESULT_HANDLE_CRASH_INFO_ERR_HANDLE_CRASH_INFO

How to test the change?

This change includes test coverage. I've also separately tried to sprinkle a few panic! calls manually and tested that it works as expected.

…h_void_ffi_result`

**What does this PR do?**

This PR updates the `wrap_with_ffi_result` and
`wrap_with_void_ffi_result` macros to catch any panics that happen
inside them, returning them as errors.

The error handling is made in such a way (see `handle_panic_error`
for details) that it should be able to report back an error even if we
fail to do any allocations.

Important note: Because only the macros have been changed, and
ffi APIs that don't use the macros are of course not affected and
can still trigger panics. If we like this approach, I'll follow-up
with a separate PR to update other APIs to use the new macros.

**Motivation:**

In <https://docs.google.com/document/d/1weMu9P03KKhPQ-gh9BMqRrEzpa1BnnY0LaSRGJbfc7A/edit?usp=sharing>
(Datadog-only link, sorry!) we saw `ddog_prof_Exporter_send`
crashing due to what can be summed up as

`ddog_prof_Exporter_send` (report a profile) ->
  hyper-util tries to do dns resolution in a separate thread pool ->
    tokio failed to create a new thread ->
      panic and we tear down the app because we can't report a profile

This is not good at all, and this PR solves this inspired by
earlier work in #815 and #1083.

**Additional Notes:**

While I don't predict that will happen very often, callers that
want to opt-out of the catch unwind behavior can still use the
`..._no_catch` variants of the macros.

The return type change in `ddog_crasht_CrashInfoBuilder_build`
does change the tag enum entries, which unfortunately is a
breaking change.

Ideas on how to work around this? This makes the following
enum entries change:

* `DDOG_CRASHT_CRASH_INFO_NEW_RESULT_OK` =>
  `DDOG_CRASHT_RESULT_HANDLE_CRASH_INFO_OK_HANDLE_CRASH_INFO`
* `DDOG_CRASHT_CRASH_INFO_NEW_RESULT_ERR` =>
  `DDOG_CRASHT_RESULT_HANDLE_CRASH_INFO_ERR_HANDLE_CRASH_INFO`

**How to test the change?**

This change includes test coverage. I've also separately tried to
sprinkle a few `panic!` calls manually and tested that it works as
expected.
Copy link
Contributor

@danielsn danielsn left a comment

Choose a reason for hiding this comment

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

What is the consumer supposed to do if there was a panic caught? We should clearly document that.

Comment on lines +16 to +23
const CANNOT_ALLOCATE: &std::ffi::CStr =
c"libdatadog failed: (panic) Cannot allocate error message";
const CANNOT_ALLOCATE_CHAR_SLICE: CharSlice = unsafe {
crate::Slice::from_raw_parts(
CANNOT_ALLOCATE.as_ptr(),
CANNOT_ALLOCATE.to_bytes_with_nul().len(),
)
};
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a way to run this with MIRI to make sure we get lifetimes right?

Copy link
Member Author

Choose a reason for hiding this comment

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

Does miri not run our regular test suite? E.g. I somewhat assumed the new tests in utils.rs would be automatically covered... aren't they? 👀

/// You probably don't want to use this directly. This constant is used by `handle_panic_error` to
/// signal that something went wrong, but avoid needing any allocations to represent it.
pub(crate) const CANNOT_ALLOCATE_ERROR: Error = Error {
message: Vec::new(),
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe add a comment for future maintainers that vec::new doesn't actually allocate

Copy link
Member Author

Choose a reason for hiding this comment

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

Docs improved in d3b2ef9

match error {
None => CharSlice::empty(),
Some(err) => CharSlice::from(err.as_ref()),
// When the error is empty (CANNOT_ALLOCATE_ERROR) we assume we failed to allocate an actual
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you need to special case ddog_error_drop to avoid dropping the static const CANNOT_ALLOCATE_ERROR

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think that's the case? Specifically ddog_error_drop receives the Error, not the CharSlice we produce here, so the ddog_error_drop should see the vec with capacity == 0, not the string produced here. But I may be missing something...? 👀

// This pattern of String vs &str comes from
// https://doc.rust-lang.org/std/panic/struct.PanicHookInfo.html#method.payload
if let Some(s) = error.downcast_ref::<String>() {
anyhow::anyhow!("{} failed: (panic) {}", function_name, s)
Copy link
Contributor

Choose a reason for hiding this comment

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

anyhow itself can allocate

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes! That's why there's this second catch_unwind: Either anyhow succeeds and we're able to return this error OR it fails and we fall back to the CANNOT_ALLOCATE_ERROR.

This way we should be able to return the nice error in most cases.

@codecov-commenter
Copy link

codecov-commenter commented Nov 11, 2025

Codecov Report

❌ Patch coverage is 81.25000% with 18 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.88%. Comparing base (3e1bd42) to head (f8017b0).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1334   +/-   ##
=======================================
  Coverage   70.87%   70.88%           
=======================================
  Files         385      385           
  Lines       61838    61924   +86     
=======================================
+ Hits        43828    43892   +64     
- Misses      18010    18032   +22     
Components Coverage Δ
datadog-crashtracker ∅ <ø> (∅)
datadog-crashtracker-ffi ∅ <ø> (∅)
datadog-alloc ∅ <ø> (∅)
data-pipeline ∅ <ø> (∅)
data-pipeline-ffi ∅ <ø> (∅)
ddcommon ∅ <ø> (∅)
ddcommon-ffi ∅ <ø> (∅)
ddtelemetry ∅ <ø> (∅)
ddtelemetry-ffi ∅ <ø> (∅)
dogstatsd-client ∅ <ø> (∅)
datadog-ipc 82.61% <ø> (ø)
datadog-profiling 0.00% <ø> (ø)
datadog-profiling-ffi ∅ <ø> (∅)
datadog-sidecar 36.17% <ø> (ø)
datdog-sidecar-ffi 12.32% <ø> (ø)
spawn-worker 55.18% <ø> (ø)
tinybytes ∅ <ø> (∅)
datadog-trace-normalization ∅ <ø> (∅)
datadog-trace-obfuscation 94.17% <ø> (ø)
datadog-trace-protobuf ∅ <ø> (∅)
datadog-trace-utils ∅ <ø> (∅)
datadog-tracer-flare 61.06% <ø> (ø)
datadog-log ∅ <ø> (∅)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@pr-commenter
Copy link

pr-commenter bot commented Nov 11, 2025

Benchmarks

Comparison

Benchmark execution time: 2025-11-14 21:12:01

Comparing candidate commit f8017b0 in PR branch ivoanjo/crash-handling-experiments with baseline commit 3e1bd42 in branch main.

Found 0 performance improvements and 0 performance regressions! Performance is the same for 55 metrics, 2 unstable metrics.

Candidate

Candidate benchmark details

Group 1

cpu_model git_commit_sha git_commit_date git_branch
Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz f8017b0 1763153832 ivoanjo/crash-handling-experiments
scenario metric min mean ± sd median ± mad p75 p95 p99 max peak_to_median_ratio skewness kurtosis cv sem runs sample_size
normalization/normalize_trace/test_trace execution_time 242.000ns 253.350ns ± 15.019ns 247.267ns ± 3.152ns 252.530ns 289.478ns 302.802ns 304.741ns 23.24% 2.006 2.980 5.91% 1.062ns 1 200
scenario metric 95% CI mean Shapiro-Wilk pvalue Ljung-Box pvalue (lag=1) Dip test pvalue
normalization/normalize_trace/test_trace execution_time [251.268ns; 255.431ns] or [-0.822%; +0.822%] None None None

Group 2

cpu_model git_commit_sha git_commit_date git_branch
Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz f8017b0 1763153832 ivoanjo/crash-handling-experiments
scenario metric min mean ± sd median ± mad p75 p95 p99 max peak_to_median_ratio skewness kurtosis cv sem runs sample_size
two way interface execution_time 17.629µs 25.256µs ± 8.401µs 18.332µs ± 0.252µs 32.174µs 39.891µs 40.891µs 52.938µs 188.77% 0.664 -0.663 33.18% 0.594µs 1 200
scenario metric 95% CI mean Shapiro-Wilk pvalue Ljung-Box pvalue (lag=1) Dip test pvalue
two way interface execution_time [24.091µs; 26.420µs] or [-4.610%; +4.610%] None None None

Group 3

cpu_model git_commit_sha git_commit_date git_branch
Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz f8017b0 1763153832 ivoanjo/crash-handling-experiments
scenario metric min mean ± sd median ± mad p75 p95 p99 max peak_to_median_ratio skewness kurtosis cv sem runs sample_size
write only interface execution_time 1.199µs 3.245µs ± 1.432µs 3.025µs ± 0.027µs 3.047µs 3.669µs 14.181µs 14.705µs 386.16% 7.293 54.550 44.01% 0.101µs 1 200
scenario metric 95% CI mean Shapiro-Wilk pvalue Ljung-Box pvalue (lag=1) Dip test pvalue
write only interface execution_time [3.047µs; 3.444µs] or [-6.114%; +6.114%] None None None

Group 4

cpu_model git_commit_sha git_commit_date git_branch
Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz f8017b0 1763153832 ivoanjo/crash-handling-experiments
scenario metric min mean ± sd median ± mad p75 p95 p99 max peak_to_median_ratio skewness kurtosis cv sem runs sample_size
benching deserializing traces from msgpack to their internal representation execution_time 60.747ms 61.149ms ± 1.832ms 60.915ms ± 0.036ms 60.954ms 61.120ms 69.481ms 81.360ms 33.56% 9.147 87.835 2.99% 0.130ms 1 200
scenario metric 95% CI mean Shapiro-Wilk pvalue Ljung-Box pvalue (lag=1) Dip test pvalue
benching deserializing traces from msgpack to their internal representation execution_time [60.895ms; 61.403ms] or [-0.415%; +0.415%] None None None

Group 5

cpu_model git_commit_sha git_commit_date git_branch
Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz f8017b0 1763153832 ivoanjo/crash-handling-experiments
scenario metric min mean ± sd median ± mad p75 p95 p99 max peak_to_median_ratio skewness kurtosis cv sem runs sample_size
receiver_entry_point/report/2597 execution_time 6.166ms 6.456ms ± 0.061ms 6.469ms ± 0.016ms 6.483ms 6.507ms 6.548ms 6.568ms 1.54% -2.783 8.828 0.94% 0.004ms 1 200
scenario metric 95% CI mean Shapiro-Wilk pvalue Ljung-Box pvalue (lag=1) Dip test pvalue
receiver_entry_point/report/2597 execution_time [6.447ms; 6.464ms] or [-0.130%; +0.130%] None None None

Group 6

cpu_model git_commit_sha git_commit_date git_branch
Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz f8017b0 1763153832 ivoanjo/crash-handling-experiments
scenario metric min mean ± sd median ± mad p75 p95 p99 max peak_to_median_ratio skewness kurtosis cv sem runs sample_size
tags/replace_trace_tags execution_time 2.422µs 2.450µs ± 0.027µs 2.441µs ± 0.010µs 2.456µs 2.517µs 2.528µs 2.536µs 3.92% 1.561 1.545 1.09% 0.002µs 1 200
scenario metric 95% CI mean Shapiro-Wilk pvalue Ljung-Box pvalue (lag=1) Dip test pvalue
tags/replace_trace_tags execution_time [2.446µs; 2.454µs] or [-0.151%; +0.151%] None None None

Group 7

cpu_model git_commit_sha git_commit_date git_branch
Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz f8017b0 1763153832 ivoanjo/crash-handling-experiments
scenario metric min mean ± sd median ± mad p75 p95 p99 max peak_to_median_ratio skewness kurtosis cv sem runs sample_size
concentrator/add_spans_to_concentrator execution_time 10.588ms 10.617ms ± 0.015ms 10.616ms ± 0.009ms 10.625ms 10.644ms 10.652ms 10.685ms 0.65% 1.102 2.895 0.14% 0.001ms 1 200
scenario metric 95% CI mean Shapiro-Wilk pvalue Ljung-Box pvalue (lag=1) Dip test pvalue
concentrator/add_spans_to_concentrator execution_time [10.615ms; 10.619ms] or [-0.019%; +0.019%] None None None

Group 8

cpu_model git_commit_sha git_commit_date git_branch
Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz f8017b0 1763153832 ivoanjo/crash-handling-experiments
scenario metric min mean ± sd median ± mad p75 p95 p99 max peak_to_median_ratio skewness kurtosis cv sem runs sample_size
normalization/normalize_name/normalize_name/Too-Long-.Too-Long-.Too-Long-.Too-Long-.Too-Long-.Too-Lo... execution_time 205.980µs 206.335µs ± 0.183µs 206.325µs ± 0.125µs 206.448µs 206.661µs 206.729µs 206.823µs 0.24% 0.275 -0.543 0.09% 0.013µs 1 200
normalization/normalize_name/normalize_name/Too-Long-.Too-Long-.Too-Long-.Too-Long-.Too-Long-.Too-Lo... throughput 4835062.304op/s 4846492.565op/s ± 4289.302op/s 4846717.325op/s ± 2933.346op/s 4849640.342op/s 4853216.468op/s 4854541.650op/s 4854850.468op/s 0.17% -0.271 -0.546 0.09% 303.299op/s 1 200
normalization/normalize_name/normalize_name/bad-name execution_time 18.174µs 18.262µs ± 0.059µs 18.247µs ± 0.019µs 18.276µs 18.396µs 18.467µs 18.602µs 1.95% 2.453 8.045 0.32% 0.004µs 1 200
normalization/normalize_name/normalize_name/bad-name throughput 53758413.305op/s 54758792.270op/s ± 174663.019op/s 54804639.881op/s ± 57645.418op/s 54850096.760op/s 54938325.268op/s 54971276.341op/s 55023125.896op/s 0.40% -2.415 7.786 0.32% 12350.541op/s 1 200
normalization/normalize_name/normalize_name/good execution_time 10.581µs 10.783µs ± 0.095µs 10.789µs ± 0.079µs 10.866µs 10.917µs 10.974µs 11.009µs 2.04% -0.032 -0.933 0.88% 0.007µs 1 200
normalization/normalize_name/normalize_name/good throughput 90835401.399op/s 92747460.268op/s ± 814205.830op/s 92689258.586op/s ± 679240.029op/s 93427650.224op/s 94081501.726op/s 94358867.661op/s 94510593.844op/s 1.96% 0.060 -0.939 0.88% 57573.046op/s 1 200
scenario metric 95% CI mean Shapiro-Wilk pvalue Ljung-Box pvalue (lag=1) Dip test pvalue
normalization/normalize_name/normalize_name/Too-Long-.Too-Long-.Too-Long-.Too-Long-.Too-Long-.Too-Lo... execution_time [206.310µs; 206.360µs] or [-0.012%; +0.012%] None None None
normalization/normalize_name/normalize_name/Too-Long-.Too-Long-.Too-Long-.Too-Long-.Too-Long-.Too-Lo... throughput [4845898.109op/s; 4847087.021op/s] or [-0.012%; +0.012%] None None None
normalization/normalize_name/normalize_name/bad-name execution_time [18.254µs; 18.270µs] or [-0.045%; +0.045%] None None None
normalization/normalize_name/normalize_name/bad-name throughput [54734585.656op/s; 54782998.885op/s] or [-0.044%; +0.044%] None None None
normalization/normalize_name/normalize_name/good execution_time [10.770µs; 10.796µs] or [-0.122%; +0.122%] None None None
normalization/normalize_name/normalize_name/good throughput [92634619.171op/s; 92860301.366op/s] or [-0.122%; +0.122%] None None None

Group 9

cpu_model git_commit_sha git_commit_date git_branch
Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz f8017b0 1763153832 ivoanjo/crash-handling-experiments
scenario metric min mean ± sd median ± mad p75 p95 p99 max peak_to_median_ratio skewness kurtosis cv sem runs sample_size
redis/obfuscate_redis_string execution_time 33.824µs 34.366µs ± 1.039µs 33.889µs ± 0.031µs 33.939µs 36.583µs 36.614µs 37.944µs 11.97% 1.740 1.178 3.02% 0.073µs 1 200
scenario metric 95% CI mean Shapiro-Wilk pvalue Ljung-Box pvalue (lag=1) Dip test pvalue
redis/obfuscate_redis_string execution_time [34.222µs; 34.510µs] or [-0.419%; +0.419%] None None None

Group 10

cpu_model git_commit_sha git_commit_date git_branch
Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz f8017b0 1763153832 ivoanjo/crash-handling-experiments
scenario metric min mean ± sd median ± mad p75 p95 p99 max peak_to_median_ratio skewness kurtosis cv sem runs sample_size
sql/obfuscate_sql_string execution_time 89.334µs 89.522µs ± 0.163µs 89.505µs ± 0.055µs 89.559µs 89.662µs 89.903µs 91.213µs 1.91% 6.588 61.219 0.18% 0.012µs 1 200
scenario metric 95% CI mean Shapiro-Wilk pvalue Ljung-Box pvalue (lag=1) Dip test pvalue
sql/obfuscate_sql_string execution_time [89.500µs; 89.545µs] or [-0.025%; +0.025%] None None None

Group 11

cpu_model git_commit_sha git_commit_date git_branch
Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz f8017b0 1763153832 ivoanjo/crash-handling-experiments
scenario metric min mean ± sd median ± mad p75 p95 p99 max peak_to_median_ratio skewness kurtosis cv sem runs sample_size
credit_card/is_card_number/ execution_time 3.896µs 3.912µs ± 0.003µs 3.912µs ± 0.002µs 3.914µs 3.917µs 3.918µs 3.919µs 0.19% -0.638 5.378 0.07% 0.000µs 1 200
credit_card/is_card_number/ throughput 255167516.576op/s 255620464.409op/s ± 178842.063op/s 255639879.856op/s ± 121940.648op/s 255749024.988op/s 255828337.051op/s 255909002.957op/s 256699753.082op/s 0.41% 0.653 5.468 0.07% 12646.044op/s 1 200
credit_card/is_card_number/ 3782-8224-6310-005 execution_time 80.216µs 81.299µs ± 0.552µs 81.299µs ± 0.387µs 81.614µs 82.310µs 82.592µs 83.439µs 2.63% 0.597 0.457 0.68% 0.039µs 1 200
credit_card/is_card_number/ 3782-8224-6310-005 throughput 11984834.799op/s 12300803.970op/s ± 83138.361op/s 12300327.245op/s ± 58502.098op/s 12363953.484op/s 12421505.369op/s 12454019.928op/s 12466367.413op/s 1.35% -0.555 0.353 0.67% 5878.770op/s 1 200
credit_card/is_card_number/ 378282246310005 execution_time 74.874µs 75.655µs ± 0.176µs 75.643µs ± 0.078µs 75.726µs 75.919µs 76.152µs 76.549µs 1.20% 0.372 4.941 0.23% 0.012µs 1 200
credit_card/is_card_number/ 378282246310005 throughput 13063511.256op/s 13217997.393op/s ± 30773.153op/s 13220066.192op/s ± 13600.261op/s 13232464.150op/s 13264723.998op/s 13287879.201op/s 13355777.735op/s 1.03% -0.325 4.885 0.23% 2175.990op/s 1 200
credit_card/is_card_number/37828224631 execution_time 3.892µs 3.913µs ± 0.003µs 3.912µs ± 0.001µs 3.914µs 3.917µs 3.919µs 3.921µs 0.23% -1.240 12.753 0.07% 0.000µs 1 200
credit_card/is_card_number/37828224631 throughput 255006904.940op/s 255581390.462op/s ± 183759.133op/s 255598670.887op/s ± 95634.287op/s 255692349.732op/s 255801433.410op/s 255833774.527op/s 256910887.805op/s 0.51% 1.269 12.966 0.07% 12993.733op/s 1 200
credit_card/is_card_number/378282246310005 execution_time 72.288µs 72.829µs ± 0.178µs 72.836µs ± 0.094µs 72.930µs 73.127µs 73.228µs 73.533µs 0.96% -0.012 1.357 0.24% 0.013µs 1 200
credit_card/is_card_number/378282246310005 throughput 13599277.386op/s 13730868.041op/s ± 33487.478op/s 13729430.043op/s ± 17619.590op/s 13747454.179op/s 13784845.495op/s 13824846.595op/s 13833520.002op/s 0.76% 0.037 1.337 0.24% 2367.922op/s 1 200
credit_card/is_card_number/37828224631000521389798 execution_time 53.058µs 53.129µs ± 0.038µs 53.118µs ± 0.017µs 53.149µs 53.210µs 53.238µs 53.252µs 0.25% 1.033 0.807 0.07% 0.003µs 1 200
credit_card/is_card_number/37828224631000521389798 throughput 18778669.141op/s 18822159.523op/s ± 13385.672op/s 18825902.191op/s ± 6113.292op/s 18830833.408op/s 18839250.482op/s 18845015.935op/s 18847336.772op/s 0.11% -1.029 0.799 0.07% 946.510op/s 1 200
credit_card/is_card_number/x371413321323331 execution_time 6.430µs 6.440µs ± 0.005µs 6.440µs ± 0.004µs 6.443µs 6.448µs 6.454µs 6.454µs 0.23% 0.314 -0.342 0.08% 0.000µs 1 200
credit_card/is_card_number/x371413321323331 throughput 154937279.814op/s 155287133.902op/s ± 125598.136op/s 155287610.592op/s ± 91894.851op/s 155386267.740op/s 155483686.762op/s 155503768.754op/s 155513384.809op/s 0.15% -0.310 -0.347 0.08% 8881.129op/s 1 200
credit_card/is_card_number_no_luhn/ execution_time 3.891µs 3.913µs ± 0.003µs 3.912µs ± 0.002µs 3.914µs 3.917µs 3.919µs 3.919µs 0.17% -1.995 17.649 0.07% 0.000µs 1 200
credit_card/is_card_number_no_luhn/ throughput 255173678.770op/s 255589593.717op/s ± 176856.073op/s 255598731.787op/s ± 98929.123op/s 255698331.568op/s 255784153.008op/s 255849863.585op/s 256983090.874op/s 0.54% 2.028 17.942 0.07% 12505.613op/s 1 200
credit_card/is_card_number_no_luhn/ 3782-8224-6310-005 execution_time 64.983µs 65.140µs ± 0.099µs 65.122µs ± 0.060µs 65.188µs 65.332µs 65.436µs 65.486µs 0.56% 0.972 0.850 0.15% 0.007µs 1 200
credit_card/is_card_number_no_luhn/ 3782-8224-6310-005 throughput 15270503.080op/s 15351527.652op/s ± 23291.590op/s 15355686.560op/s ± 14094.254op/s 15369204.341op/s 15382061.670op/s 15387249.957op/s 15388635.493op/s 0.21% -0.964 0.826 0.15% 1646.964op/s 1 200
credit_card/is_card_number_no_luhn/ 378282246310005 execution_time 58.190µs 58.424µs ± 0.141µs 58.408µs ± 0.095µs 58.504µs 58.685µs 58.804µs 58.850µs 0.76% 0.700 -0.002 0.24% 0.010µs 1 200
credit_card/is_card_number_no_luhn/ 378282246310005 throughput 16992240.967op/s 17116233.309op/s ± 41250.062op/s 17120803.455op/s ± 27922.827op/s 17148692.835op/s 17168358.036op/s 17183436.181op/s 17185138.292op/s 0.38% -0.689 -0.025 0.24% 2916.820op/s 1 200
credit_card/is_card_number_no_luhn/37828224631 execution_time 3.893µs 3.912µs ± 0.003µs 3.912µs ± 0.001µs 3.914µs 3.918µs 3.920µs 3.922µs 0.25% -0.853 10.968 0.07% 0.000µs 1 200
credit_card/is_card_number_no_luhn/37828224631 throughput 254983119.339op/s 255594745.660op/s ± 183168.241op/s 255614991.819op/s ± 82428.900op/s 255687745.650op/s 255795002.781op/s 255889945.241op/s 256857239.128op/s 0.49% 0.879 11.137 0.07% 12951.951op/s 1 200
credit_card/is_card_number_no_luhn/378282246310005 execution_time 55.161µs 55.483µs ± 0.172µs 55.457µs ± 0.107µs 55.576µs 55.827µs 55.956µs 56.014µs 1.00% 0.748 0.275 0.31% 0.012µs 1 200
credit_card/is_card_number_no_luhn/378282246310005 throughput 17852757.166op/s 18023710.461op/s ± 55816.530op/s 18032085.762op/s ± 34917.834op/s 18064360.377op/s 18100288.609op/s 18119938.344op/s 18128823.016op/s 0.54% -0.732 0.246 0.31% 3946.825op/s 1 200
credit_card/is_card_number_no_luhn/37828224631000521389798 execution_time 53.049µs 53.123µs ± 0.037µs 53.117µs ± 0.015µs 53.133µs 53.202µs 53.236µs 53.326µs 0.39% 1.652 4.891 0.07% 0.003µs 1 200
credit_card/is_card_number_no_luhn/37828224631000521389798 throughput 18752416.606op/s 18824125.529op/s ± 13173.931op/s 18826345.688op/s ± 5428.012op/s 18831703.063op/s 18840349.853op/s 18846047.537op/s 18850444.068op/s 0.13% -1.643 4.844 0.07% 931.538op/s 1 200
credit_card/is_card_number_no_luhn/x371413321323331 execution_time 6.429µs 6.441µs ± 0.006µs 6.440µs ± 0.004µs 6.445µs 6.453µs 6.456µs 6.465µs 0.39% 0.714 0.498 0.10% 0.000µs 1 200
credit_card/is_card_number_no_luhn/x371413321323331 throughput 154670876.211op/s 155248974.947op/s ± 148194.569op/s 155277686.597op/s ± 94367.195op/s 155353014.216op/s 155450798.159op/s 155513150.523op/s 155548332.913op/s 0.17% -0.709 0.483 0.10% 10478.938op/s 1 200
scenario metric 95% CI mean Shapiro-Wilk pvalue Ljung-Box pvalue (lag=1) Dip test pvalue
credit_card/is_card_number/ execution_time [3.912µs; 3.912µs] or [-0.010%; +0.010%] None None None
credit_card/is_card_number/ throughput [255595678.619op/s; 255645250.199op/s] or [-0.010%; +0.010%] None None None
credit_card/is_card_number/ 3782-8224-6310-005 execution_time [81.223µs; 81.376µs] or [-0.094%; +0.094%] None None None
credit_card/is_card_number/ 3782-8224-6310-005 throughput [12289281.793op/s; 12312326.147op/s] or [-0.094%; +0.094%] None None None
credit_card/is_card_number/ 378282246310005 execution_time [75.630µs; 75.679µs] or [-0.032%; +0.032%] None None None
credit_card/is_card_number/ 378282246310005 throughput [13213732.530op/s; 13222262.256op/s] or [-0.032%; +0.032%] None None None
credit_card/is_card_number/37828224631 execution_time [3.912µs; 3.913µs] or [-0.010%; +0.010%] None None None
credit_card/is_card_number/37828224631 throughput [255555923.213op/s; 255606857.710op/s] or [-0.010%; +0.010%] None None None
credit_card/is_card_number/378282246310005 execution_time [72.804µs; 72.854µs] or [-0.034%; +0.034%] None None None
credit_card/is_card_number/378282246310005 throughput [13726226.998op/s; 13735509.083op/s] or [-0.034%; +0.034%] None None None
credit_card/is_card_number/37828224631000521389798 execution_time [53.124µs; 53.134µs] or [-0.010%; +0.010%] None None None
credit_card/is_card_number/37828224631000521389798 throughput [18820304.397op/s; 18824014.648op/s] or [-0.010%; +0.010%] None None None
credit_card/is_card_number/x371413321323331 execution_time [6.439µs; 6.440µs] or [-0.011%; +0.011%] None None None
credit_card/is_card_number/x371413321323331 throughput [155269727.208op/s; 155304540.595op/s] or [-0.011%; +0.011%] None None None
credit_card/is_card_number_no_luhn/ execution_time [3.912µs; 3.913µs] or [-0.010%; +0.010%] None None None
credit_card/is_card_number_no_luhn/ throughput [255565083.166op/s; 255614104.268op/s] or [-0.010%; +0.010%] None None None
credit_card/is_card_number_no_luhn/ 3782-8224-6310-005 execution_time [65.127µs; 65.154µs] or [-0.021%; +0.021%] None None None
credit_card/is_card_number_no_luhn/ 3782-8224-6310-005 throughput [15348299.661op/s; 15354755.642op/s] or [-0.021%; +0.021%] None None None
credit_card/is_card_number_no_luhn/ 378282246310005 execution_time [58.405µs; 58.444µs] or [-0.033%; +0.033%] None None None
credit_card/is_card_number_no_luhn/ 378282246310005 throughput [17110516.447op/s; 17121950.171op/s] or [-0.033%; +0.033%] None None None
credit_card/is_card_number_no_luhn/37828224631 execution_time [3.912µs; 3.913µs] or [-0.010%; +0.010%] None None None
credit_card/is_card_number_no_luhn/37828224631 throughput [255569360.303op/s; 255620131.017op/s] or [-0.010%; +0.010%] None None None
credit_card/is_card_number_no_luhn/378282246310005 execution_time [55.459µs; 55.507µs] or [-0.043%; +0.043%] None None None
credit_card/is_card_number_no_luhn/378282246310005 throughput [18015974.827op/s; 18031446.095op/s] or [-0.043%; +0.043%] None None None
credit_card/is_card_number_no_luhn/37828224631000521389798 execution_time [53.118µs; 53.129µs] or [-0.010%; +0.010%] None None None
credit_card/is_card_number_no_luhn/37828224631000521389798 throughput [18822299.749op/s; 18825951.309op/s] or [-0.010%; +0.010%] None None None
credit_card/is_card_number_no_luhn/x371413321323331 execution_time [6.440µs; 6.442µs] or [-0.013%; +0.013%] None None None
credit_card/is_card_number_no_luhn/x371413321323331 throughput [155228436.605op/s; 155269513.289op/s] or [-0.013%; +0.013%] None None None

Group 12

cpu_model git_commit_sha git_commit_date git_branch
Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz f8017b0 1763153832 ivoanjo/crash-handling-experiments
scenario metric min mean ± sd median ± mad p75 p95 p99 max peak_to_median_ratio skewness kurtosis cv sem runs sample_size
sdk_test_data/rules-based execution_time 144.587µs 147.084µs ± 1.630µs 146.776µs ± 0.459µs 147.324µs 149.274µs 154.683µs 161.769µs 10.21% 5.056 36.669 1.11% 0.115µs 1 200
scenario metric 95% CI mean Shapiro-Wilk pvalue Ljung-Box pvalue (lag=1) Dip test pvalue
sdk_test_data/rules-based execution_time [146.858µs; 147.310µs] or [-0.154%; +0.154%] None None None

Group 13

cpu_model git_commit_sha git_commit_date git_branch
Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz f8017b0 1763153832 ivoanjo/crash-handling-experiments
scenario metric min mean ± sd median ± mad p75 p95 p99 max peak_to_median_ratio skewness kurtosis cv sem runs sample_size
single_flag_killswitch/rules-based execution_time 190.043ns 193.060ns ± 2.448ns 192.716ns ± 1.519ns 194.195ns 197.526ns 198.697ns 209.202ns 8.55% 1.932 8.436 1.26% 0.173ns 1 200
scenario metric 95% CI mean Shapiro-Wilk pvalue Ljung-Box pvalue (lag=1) Dip test pvalue
single_flag_killswitch/rules-based execution_time [192.720ns; 193.399ns] or [-0.176%; +0.176%] None None None

Group 14

cpu_model git_commit_sha git_commit_date git_branch
Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz f8017b0 1763153832 ivoanjo/crash-handling-experiments
scenario metric min mean ± sd median ± mad p75 p95 p99 max peak_to_median_ratio skewness kurtosis cv sem runs sample_size
ip_address/quantize_peer_ip_address_benchmark execution_time 4.935µs 5.003µs ± 0.037µs 5.006µs ± 0.036µs 5.033µs 5.046µs 5.051µs 5.051µs 0.90% -0.408 -1.395 0.74% 0.003µs 1 200
scenario metric 95% CI mean Shapiro-Wilk pvalue Ljung-Box pvalue (lag=1) Dip test pvalue
ip_address/quantize_peer_ip_address_benchmark execution_time [4.998µs; 5.008µs] or [-0.103%; +0.103%] None None None

Group 15

cpu_model git_commit_sha git_commit_date git_branch
Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz f8017b0 1763153832 ivoanjo/crash-handling-experiments
scenario metric min mean ± sd median ± mad p75 p95 p99 max peak_to_median_ratio skewness kurtosis cv sem runs sample_size
benching serializing traces from their internal representation to msgpack execution_time 14.846ms 14.907ms ± 0.031ms 14.904ms ± 0.015ms 14.917ms 14.973ms 15.022ms 15.026ms 0.82% 1.437 3.324 0.21% 0.002ms 1 200
scenario metric 95% CI mean Shapiro-Wilk pvalue Ljung-Box pvalue (lag=1) Dip test pvalue
benching serializing traces from their internal representation to msgpack execution_time [14.903ms; 14.911ms] or [-0.029%; +0.029%] None None None

Group 16

cpu_model git_commit_sha git_commit_date git_branch
Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz f8017b0 1763153832 ivoanjo/crash-handling-experiments
scenario metric min mean ± sd median ± mad p75 p95 p99 max peak_to_median_ratio skewness kurtosis cv sem runs sample_size
benching string interning on wordpress profile execution_time 161.157µs 161.901µs ± 0.433µs 161.812µs ± 0.146µs 161.992µs 162.519µs 162.897µs 165.945µs 2.55% 4.783 38.310 0.27% 0.031µs 1 200
scenario metric 95% CI mean Shapiro-Wilk pvalue Ljung-Box pvalue (lag=1) Dip test pvalue
benching string interning on wordpress profile execution_time [161.841µs; 161.961µs] or [-0.037%; +0.037%] None None None

Group 17

cpu_model git_commit_sha git_commit_date git_branch
Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz f8017b0 1763153832 ivoanjo/crash-handling-experiments
scenario metric min mean ± sd median ± mad p75 p95 p99 max peak_to_median_ratio skewness kurtosis cv sem runs sample_size
normalization/normalize_service/normalize_service/A0000000000000000000000000000000000000000000000000... execution_time 533.812µs 534.647µs ± 0.987µs 534.531µs ± 0.233µs 534.751µs 535.342µs 537.737µs 546.766µs 2.29% 9.657 112.957 0.18% 0.070µs 1 200
normalization/normalize_service/normalize_service/A0000000000000000000000000000000000000000000000000... throughput 1828937.142op/s 1870400.895op/s ± 3392.371op/s 1870799.505op/s ± 814.962op/s 1871668.499op/s 1872351.480op/s 1872984.869op/s 1873317.458op/s 0.13% -9.537 110.849 0.18% 239.877op/s 1 200
normalization/normalize_service/normalize_service/Data🐨dog🐶 繋がっ⛰てて execution_time 379.957µs 380.786µs ± 0.461µs 380.730µs ± 0.242µs 380.997µs 381.481µs 381.827µs 383.696µs 0.78% 2.434 12.843 0.12% 0.033µs 1 200
normalization/normalize_service/normalize_service/Data🐨dog🐶 繋がっ⛰てて throughput 2606231.704op/s 2626151.311op/s ± 3169.760op/s 2626536.388op/s ± 1671.569op/s 2628012.037op/s 2629924.172op/s 2631525.137op/s 2631875.259op/s 0.20% -2.402 12.600 0.12% 224.136op/s 1 200
normalization/normalize_service/normalize_service/Test Conversion 0f Weird !@#$%^&**() Characters execution_time 194.416µs 194.840µs ± 0.164µs 194.820µs ± 0.107µs 194.934µs 195.152µs 195.245µs 195.355µs 0.27% 0.539 0.099 0.08% 0.012µs 1 200
normalization/normalize_service/normalize_service/Test Conversion 0f Weird !@#$%^&**() Characters throughput 5118888.199op/s 5132421.216op/s ± 4320.071op/s 5132949.123op/s ± 2819.826op/s 5135601.503op/s 5138682.423op/s 5139746.307op/s 5143608.883op/s 0.21% -0.534 0.093 0.08% 305.475op/s 1 200
normalization/normalize_service/normalize_service/[empty string] execution_time 36.938µs 37.146µs ± 0.085µs 37.148µs ± 0.063µs 37.205µs 37.281µs 37.324µs 37.397µs 0.67% 0.073 -0.452 0.23% 0.006µs 1 200
normalization/normalize_service/normalize_service/[empty string] throughput 26740207.906op/s 26921220.451op/s ± 61547.925op/s 26919284.706op/s ± 45915.678op/s 26968292.734op/s 27018527.651op/s 27034605.583op/s 27072539.853op/s 0.57% -0.062 -0.459 0.23% 4352.096op/s 1 200
normalization/normalize_service/normalize_service/test_ASCII execution_time 44.897µs 45.090µs ± 0.159µs 45.066µs ± 0.140µs 45.238µs 45.361µs 45.432µs 45.454µs 0.86% 0.407 -1.163 0.35% 0.011µs 1 200
normalization/normalize_service/normalize_service/test_ASCII throughput 22000457.610op/s 22178258.519op/s ± 78247.607op/s 22189896.204op/s ± 69094.573op/s 22258536.613op/s 22267185.727op/s 22271673.366op/s 22272982.625op/s 0.37% -0.400 -1.174 0.35% 5532.941op/s 1 200
scenario metric 95% CI mean Shapiro-Wilk pvalue Ljung-Box pvalue (lag=1) Dip test pvalue
normalization/normalize_service/normalize_service/A0000000000000000000000000000000000000000000000000... execution_time [534.510µs; 534.783µs] or [-0.026%; +0.026%] None None None
normalization/normalize_service/normalize_service/A0000000000000000000000000000000000000000000000000... throughput [1869930.745op/s; 1870871.045op/s] or [-0.025%; +0.025%] None None None
normalization/normalize_service/normalize_service/Data🐨dog🐶 繋がっ⛰てて execution_time [380.722µs; 380.850µs] or [-0.017%; +0.017%] None None None
normalization/normalize_service/normalize_service/Data🐨dog🐶 繋がっ⛰てて throughput [2625712.012op/s; 2626590.609op/s] or [-0.017%; +0.017%] None None None
normalization/normalize_service/normalize_service/Test Conversion 0f Weird !@#$%^&**() Characters execution_time [194.817µs; 194.863µs] or [-0.012%; +0.012%] None None None
normalization/normalize_service/normalize_service/Test Conversion 0f Weird !@#$%^&**() Characters throughput [5131822.496op/s; 5133019.936op/s] or [-0.012%; +0.012%] None None None
normalization/normalize_service/normalize_service/[empty string] execution_time [37.134µs; 37.157µs] or [-0.032%; +0.032%] None None None
normalization/normalize_service/normalize_service/[empty string] throughput [26912690.500op/s; 26929750.401op/s] or [-0.032%; +0.032%] None None None
normalization/normalize_service/normalize_service/test_ASCII execution_time [45.068µs; 45.112µs] or [-0.049%; +0.049%] None None None
normalization/normalize_service/normalize_service/test_ASCII throughput [22167414.153op/s; 22189102.885op/s] or [-0.049%; +0.049%] None None None

Baseline

Omitted due to size.

Comment on lines +56 to +65
// A Rust Vec of size 0 [has no allocated memory](https://doc.rust-lang.org/std/vec/struct.Vec.html#guarantees):
// "In particular, if you construct a Vec with capacity 0 via Vec::new, vec![],
// Vec::with_capacity(0), or by calling shrink_to_fit on an empty Vec, it will not allocate
// memory." And as per https://doc.rust-lang.org/nomicon/vec/vec-dealloc.html:
// "We must not call alloc::dealloc when self.cap == 0, as in this case we haven't actually
// allocated any memory."
if self.capacity == 0 {
return;
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Was this necessary in some case?

This should naturally be handled by Drop for std::vec::Vec, because it's safe to drop an empty std::vec::Vec. After creating a std::vec::Vec of its contents, the FFI Vec will now be "dropped" but drops on pointers do nothing. I can't see why it would be necessary to early-return here.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good question... At some point in my experiments I was using the mockalloc crate and it complained that we were still trying to drop the vec...

But I'm not confident if that's a real issue with our code or just some artifact of that crate/how I was setting up my test.

As you mentioned the alloc::vec::Vec::from_raw_parts doc kinda states that ptr only needs to be non-null and aligned if capacity is zero (e.g. doesn't need to be allocated using the global allocator) which means it won't touch it.

I'll leave it here to see if anyone has any ideas on why this may or may not be needed; and if nobody else chimes in I'll undo this part of the change.

@danielsn
Copy link
Contributor

If we do catch_unwind, we lose the rust frames for debugging. Anything we can do about that?

@danielsn
Copy link
Contributor

Possibly we should publish a crash report when this panic happens?

@ivoanjo
Copy link
Member Author

ivoanjo commented Nov 12, 2025

Possibly we should publish a crash report when this panic happens?

That would be cool! As a bonus we could actually include the panic message, which right now we don't get.

@dd-octo-sts
Copy link

dd-octo-sts bot commented Nov 14, 2025

Artifact Size Benchmark Report

aarch64-alpine-linux-musl
Artifact Baseline Commit Change
/aarch64-alpine-linux-musl/lib/libdatadog_profiling.so 7.32 MB 7.32 MB 0% (0 B) 👌
/aarch64-alpine-linux-musl/lib/libdatadog_profiling.a 84.53 MB 84.81 MB +.33% (+289.87 KB) 🔍
aarch64-unknown-linux-gnu
Artifact Baseline Commit Change
/aarch64-unknown-linux-gnu/lib/libdatadog_profiling.so 9.36 MB 9.42 MB +.68% (+66.09 KB) 🔍
/aarch64-unknown-linux-gnu/lib/libdatadog_profiling.a 98.10 MB 98.43 MB +.34% (+344.66 KB) 🔍
libdatadog-x64-windows
Artifact Baseline Commit Change
/libdatadog-x64-windows/debug/dynamic/datadog_profiling_ffi.dll 18.92 MB 19.04 MB +.60% (+117.50 KB) 🔍
/libdatadog-x64-windows/debug/dynamic/datadog_profiling_ffi.lib 65.49 KB 65.49 KB 0% (0 B) 👌
/libdatadog-x64-windows/debug/dynamic/datadog_profiling_ffi.pdb 132.79 MB 133.62 MB +.62% (+856.00 KB) 🔍
/libdatadog-x64-windows/debug/static/datadog_profiling_ffi.lib 709.49 MB 714.51 MB +.70% (+5.01 MB) 🔍
/libdatadog-x64-windows/release/dynamic/datadog_profiling_ffi.dll 6.18 MB 6.21 MB +.41% (+26.50 KB) 🔍
/libdatadog-x64-windows/release/dynamic/datadog_profiling_ffi.lib 65.49 KB 65.49 KB 0% (0 B) 👌
/libdatadog-x64-windows/release/dynamic/datadog_profiling_ffi.pdb 19.25 MB 19.30 MB +.24% (+48.00 KB) 🔍
/libdatadog-x64-windows/release/static/datadog_profiling_ffi.lib 37.83 MB 37.94 MB +.29% (+113.80 KB) 🔍
libdatadog-x86-windows
Artifact Baseline Commit Change
/libdatadog-x86-windows/debug/dynamic/datadog_profiling_ffi.dll 16.06 MB 16.16 MB +.62% (+103.00 KB) 🔍
/libdatadog-x86-windows/debug/dynamic/datadog_profiling_ffi.lib 66.50 KB 66.50 KB 0% (0 B) 👌
/libdatadog-x86-windows/debug/dynamic/datadog_profiling_ffi.pdb 134.97 MB 135.87 MB +.66% (+920.00 KB) 🔍
/libdatadog-x86-windows/debug/static/datadog_profiling_ffi.lib 697.83 MB 702.81 MB +.71% (+4.98 MB) 🔍
/libdatadog-x86-windows/release/dynamic/datadog_profiling_ffi.dll 4.74 MB 4.76 MB +.37% (+18.00 KB) 🔍
/libdatadog-x86-windows/release/dynamic/datadog_profiling_ffi.lib 66.50 KB 66.50 KB 0% (0 B) 👌
/libdatadog-x86-windows/release/dynamic/datadog_profiling_ffi.pdb 20.43 MB 20.51 MB +.38% (+80.00 KB) 🔍
/libdatadog-x86-windows/release/static/datadog_profiling_ffi.lib 35.60 MB 35.73 MB +.34% (+125.25 KB) 🔍
x86_64-alpine-linux-musl
Artifact Baseline Commit Change
/x86_64-alpine-linux-musl/lib/libdatadog_profiling.a 72.97 MB 73.25 MB +.37% (+279.41 KB) 🔍
/x86_64-alpine-linux-musl/lib/libdatadog_profiling.so 8.71 MB 8.73 MB +.13% (+12.00 KB) 🔍
x86_64-unknown-linux-gnu
Artifact Baseline Commit Change
/x86_64-unknown-linux-gnu/lib/libdatadog_profiling.a 92.47 MB 92.80 MB +.35% (+333.74 KB) 🔍
/x86_64-unknown-linux-gnu/lib/libdatadog_profiling.so 10.10 MB 10.12 MB +.11% (+12.17 KB) 🔍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants