@@ -26,9 +26,10 @@ pub const MetricExporter = struct {
26
26
27
27
allocator : std.mem.Allocator ,
28
28
exporter : * ExporterIface ,
29
- hasShutDown : std .atomic .Value (bool ) = std .atomic .Value (bool ).init (false ),
30
29
31
- var exportCompleted : std .atomic .Value (bool ) = std .atomic .Value (bool ).init (false );
30
+ // Lock helper to signal shutdown and/or export is in progress
31
+ hasShutDown : bool = false ,
32
+ exportCompleted : std.Thread.Mutex = std.Thread.Mutex {},
32
33
33
34
pub fn new (allocator : std.mem.Allocator , exporter : * ExporterIface ) ! * Self {
34
35
const s = try allocator .create (Self );
@@ -42,14 +43,14 @@ pub const MetricExporter = struct {
42
43
/// ExportBatch exports a batch of metrics data by calling the exporter implementation.
43
44
/// The passed metrics data will be owned by the exporter implementation.
44
45
pub fn exportBatch (self : * Self , metrics : []Measurements ) ExportResult {
45
- if (self .hasShutDown . load ( .acquire )) {
46
+ if (@atomicLoad ( bool , & self .hasShutDown , .acquire )) {
46
47
// When shutdown has already been called, calling export should be a failure.
47
48
// https://opentelemetry.io/docs/specs/otel/metrics/sdk/#shutdown-2
48
49
return ExportResult .Failure ;
49
50
}
50
- // Acquire the lock to ensure that forceFlush is waiting for export to complete.
51
- _ = exportCompleted .load ( .acquire );
52
- defer exportCompleted .store ( true , .release );
51
+ // Acquire the lock to signal to forceFlush to wait for export to complete.
52
+ self . exportCompleted .lock ( );
53
+ defer self . exportCompleted .unlock ( );
53
54
54
55
// Call the exporter function to process metrics data.
55
56
self .exporter .exportBatch (metrics ) catch | e | {
@@ -60,24 +61,31 @@ pub const MetricExporter = struct {
60
61
}
61
62
62
63
// Ensure that all the data is flushed to the destination.
63
- pub fn forceFlush (_ : Self , timeout_ms : u64 ) ! void {
64
+ pub fn forceFlush (self : * Self , timeout_ms : u64 ) ! void {
64
65
const start = std .time .milliTimestamp (); // Milliseconds
65
66
const timeout : i64 = @intCast (timeout_ms );
66
67
while (std .time .milliTimestamp () < start + timeout ) {
67
- if (exportCompleted .load (.acquire )) {
68
+ if (self .exportCompleted .tryLock ()) {
69
+ self .exportCompleted .unlock ();
68
70
return ;
69
- } else std .time .sleep (std .time .ns_per_ms );
71
+ } else {
72
+ std .time .sleep (std .time .ns_per_ms );
73
+ }
70
74
}
71
75
return MetricReadError .ForceFlushTimedOut ;
72
76
}
73
77
74
78
pub fn shutdown (self : * Self ) void {
75
- if (self .hasShutDown .load (.acquire )) {
76
- // When shutdown has already been called, calling shutdown again is a no-op.
79
+ if (@atomicRmw (bool , & self .hasShutDown , .Xchg , true , .acq_rel )) {
77
80
return ;
78
81
}
79
- self .hasShutDown .store (true , .release );
82
+ // if (@atomicLoad(bool, &self.hasShutDown, .acquire)) {
83
+ // // When shutdown has already been called, calling shutdown again is a no-op.
84
+ // return;
85
+ // } else {
86
+ // @atomicStore(bool, &self.hasShutDown, true, .release);
80
87
self .allocator .destroy (self );
88
+ // }
81
89
}
82
90
};
83
91
@@ -115,7 +123,7 @@ test "metric exporter no-op" {
115
123
116
124
var measure = [1 ]DataPoint (i64 ){.{ .value = 42 }};
117
125
const measurement : []DataPoint (i64 ) = measure [0.. ];
118
- var metrics = [1 ]Measurements {Measurements {
126
+ var metrics = [1 ]Measurements {. {
119
127
.meterName = "my-meter" ,
120
128
.instrumentKind = .Counter ,
121
129
.instrumentOptions = .{ .name = "my-counter" },
@@ -189,9 +197,8 @@ test "metric exporter force flush fails" {
189
197
backgroundRunner ,
190
198
.{ me , & metrics },
191
199
);
192
- bg .detach ();
200
+ bg .join ();
193
201
194
- std .time .sleep (10 * std .time .ns_per_ms ); // sleep for 10 ms to ensure the background thread completed
195
202
const e = me .forceFlush (0 );
196
203
try std .testing .expectError (MetricReadError .ForceFlushTimedOut , e );
197
204
}
@@ -210,14 +217,16 @@ pub const ExporterIface = struct {
210
217
};
211
218
212
219
/// InMemoryExporter stores in memory the metrics data to be exported.
213
- /// The memory representation uses the types defined in the library.
220
+ /// The metics' representation in memory uses the types defined in the library.
214
221
pub const InMemoryExporter = struct {
215
222
const Self = @This ();
216
223
allocator : std.mem.Allocator ,
217
224
data : std .ArrayList (Measurements ) = undefined ,
218
225
// Implement the interface via @fieldParentPtr
219
226
exporter : ExporterIface ,
220
227
228
+ mx : std.Thread.Mutex = std.Thread.Mutex {},
229
+
221
230
pub fn init (allocator : std.mem.Allocator ) ! * Self {
222
231
const s = try allocator .create (Self );
223
232
s .* = Self {
@@ -230,30 +239,35 @@ pub const InMemoryExporter = struct {
230
239
return s ;
231
240
}
232
241
pub fn deinit (self : * Self ) void {
233
- for ( self .data . items ) | d | {
234
- var data = d ;
235
- data .deinit (self .allocator );
242
+ self .mx . lock ();
243
+ for ( self . data . items ) | * d | {
244
+ d .* .deinit (self .allocator );
236
245
}
237
246
self .data .deinit ();
247
+ self .mx .unlock ();
248
+
238
249
self .allocator .destroy (self );
239
250
}
240
251
252
+ // Implements the ExportIFace interface only method.
241
253
fn exportBatch (iface : * ExporterIface , metrics : []Measurements ) MetricReadError ! void {
242
254
// Get a pointer to the instance of the struct that implements the interface.
243
255
const self : * Self = @fieldParentPtr ("exporter" , iface );
256
+ self .mx .lock ();
257
+ defer self .mx .unlock ();
244
258
245
- for ( self . data . items ) | d | {
246
- var data = d ;
247
- data .deinit (self .allocator );
259
+ // Free up the allocated data points from the previous export.
260
+ for ( self . data . items ) | * d | {
261
+ d .* .deinit (self .allocator );
248
262
}
249
- self .data .clearRetainingCapacity ();
263
+ self .data .clearAndFree ();
250
264
self .data = std .ArrayList (Measurements ).fromOwnedSlice (self .allocator , metrics );
251
265
}
252
266
253
267
/// Read the metrics from the in memory exporter.
254
- //FIXME might need a mutex in the exporter as the field might be accessed
255
- // from a thread while it's being cleared in another (via exportBatch).
256
268
pub fn fetch (self : * Self ) ! []Measurements {
269
+ self .mx .lock ();
270
+ defer self .mx .unlock ();
257
271
return self .data .items ;
258
272
}
259
273
};
@@ -321,7 +335,7 @@ pub const PeriodicExportingReader = struct {
321
335
exportTimeoutMillis : u64 ,
322
336
323
337
// Lock helper to signal shutdown is in progress
324
- shuttingDown : std . atomic . Value ( bool ) = std . atomic . Value ( bool ). init ( false ) ,
338
+ shuttingDown : bool = false ,
325
339
326
340
// This reader will collect metrics data from the MeterProvider.
327
341
reader : * MetricReader ,
@@ -361,8 +375,10 @@ pub const PeriodicExportingReader = struct {
361
375
}
362
376
363
377
pub fn shutdown (self : * Self ) void {
364
- self .shuttingDown .store (true , .release );
378
+ // First signal the background exporter to stop collecting, then close the reader.
379
+ @atomicStore (bool , & self .shuttingDown , true , .release );
365
380
self .reader .shutdown ();
381
+ // Only when the background collector has stopped we can destroy.
366
382
self .allocator .destroy (self );
367
383
}
368
384
};
@@ -371,17 +387,17 @@ pub const PeriodicExportingReader = struct {
371
387
// FIXME there is not a timeout for the collect operation.
372
388
fn collectAndExport (
373
389
reader : * MetricReader ,
374
- shuttingDown : * std . atomic . Value ( bool ) ,
390
+ shuttingDown : * bool ,
375
391
exportIntervalMillis : u64 ,
376
392
// TODO: add a timeout for the export operation
377
393
_ : u64 ,
378
394
) void {
379
395
// The execution should continue until the reader is shutting down
380
- while (! shuttingDown .* . load ( .acquire )) {
396
+ while (! @atomicLoad ( bool , shuttingDown , .acquire )) {
381
397
if (reader .meterProvider ) | _ | {
382
398
// This will also call exporter.exportBatch() every interval.
383
399
reader .collect () catch | e | {
384
- std .debug .print ("PeriodicExportingReader: reader collect failed: {?}\n " , .{e });
400
+ std .debug .print ("PeriodicExportingReader: collecting failed on reader : {?}\n " , .{e });
385
401
};
386
402
} else {
387
403
std .debug .print ("PeriodicExportingReader: no meter provider is registered with this MetricReader {any}\n " , .{reader });
0 commit comments