Skip to content

Commit 13c53fc

Browse files
fix: various memory issues (#35)
* fix: remove wee alloc due to memory leaks * fix: copy data to avoid use after free issues * fix: ResolveLogger improvements and flakey test fix * fixup! fix: ResolveLogger improvements and flakey test fix
1 parent 5296dc6 commit 13c53fc

File tree

6 files changed

+57
-71
lines changed

6 files changed

+57
-71
lines changed

Cargo.lock

Lines changed: 7 additions & 32 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

confidence-resolver/src/resolve_logger.rs

Lines changed: 43 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,23 @@ impl ResolveLogger {
4343
}
4444
}
4545

46-
fn with_state<F: FnOnce(&ResolveInfoState)>(&self, f: F) -> Fallible<()> {
46+
fn with_state<F: FnOnce(&ResolveInfoState)>(&self, f: F) {
4747
loop {
4848
let lock = self.state.load_full();
4949
let Ok(rg) = lock.try_read() else {
5050
// this is lock free. If we didn't get the read lock it means checkpoint has
5151
// swapped and acquired the write lock so we can just retry and get the next state
5252
continue;
5353
};
54-
let state = rg.as_ref().or_fail()?;
55-
f(state);
56-
break Ok(());
54+
// In an earlier version we failed on this Option being None, leading to flakey tests.
55+
// The Option can be none if thread T1 has a reference to the lock, but parks before try_lock.
56+
// In the meantime a checkpoint thread T2, swaps out the lock, takes a write lock, takes the option
57+
// (replacing it with None) and releases the lock. Now T1 wakes up and tries and succeeds the read
58+
// lock. This scenario is rare and as above it's sound to retry,
59+
if let Some(state) = rg.as_ref() {
60+
f(state);
61+
break;
62+
};
5763
}
5864
}
5965

@@ -63,7 +69,7 @@ impl ResolveLogger {
6369
resolve_context: &pb::Struct,
6470
client_credential: &str,
6571
values: &[crate::ResolvedValue<'_>],
66-
) -> Fallible<()> {
72+
) {
6773
self.with_state(|state: &ResolveInfoState| {
6874
state
6975
.client_resolve_info
@@ -121,7 +127,7 @@ impl ResolveLogger {
121127
assigned_flags: &[crate::FlagToApply],
122128
client: &crate::Client,
123129
sdk: &Option<crate::flags_resolver::Sdk>,
124-
) -> Fallible<()> {
130+
) {
125131
self.with_state(|state: &ResolveInfoState| {
126132
let client_info = Some(pb::ClientInfo {
127133
client: client.client_name.to_string(),
@@ -181,22 +187,29 @@ impl ResolveLogger {
181187
})
182188
}
183189

184-
pub fn checkpoint(&self) -> Fallible<pb::WriteFlagLogsRequest> {
185-
let state = {
186-
let lock = self
187-
.state
188-
.swap(Arc::new(RwLock::new(Some(ResolveInfoState::default()))));
189-
let mut wg = lock.write().or_fail()?;
190-
wg.take().or_fail()?
191-
};
192-
let client_resolve_info = build_client_resolve_info(&state);
193-
let flag_resolve_info = build_flag_resolve_info(&state);
194-
Ok(pb::WriteFlagLogsRequest {
195-
flag_resolve_info,
196-
client_resolve_info,
197-
flag_assigned: state.flag_assigned.into_iter().collect(),
198-
telemetry_data: None,
199-
})
190+
pub fn checkpoint(&self) -> pb::WriteFlagLogsRequest {
191+
let lock = self
192+
.state
193+
.swap(Arc::new(RwLock::new(Some(ResolveInfoState::default()))));
194+
// the only operation we do under write-lock is take the option, and that can't panic, so lock shouldn't be poisoned,
195+
// even so, if it some how was it's safe to still use the value.
196+
let mut wg = lock
197+
.write()
198+
.unwrap_or_else(|poisoned| poisoned.into_inner());
199+
// also shouldn't be possible for this Option to be None as we never insert None and only one thread can swap the value out
200+
// if this assertion somehow is faulty, returning an empty WriteFlagLogsRequest is sound.
201+
wg.take()
202+
.map(|state| {
203+
let client_resolve_info = build_client_resolve_info(&state);
204+
let flag_resolve_info = build_flag_resolve_info(&state);
205+
pb::WriteFlagLogsRequest {
206+
flag_resolve_info,
207+
client_resolve_info,
208+
flag_assigned: state.flag_assigned.into_iter().collect(),
209+
telemetry_data: None,
210+
}
211+
})
212+
.unwrap_or_default()
200213
}
201214
}
202215

@@ -373,7 +386,7 @@ mod tests {
373386
let cred = "clients/test/clientCredentials/test";
374387
let rv = [];
375388
logger.log_resolve("id", &ctx, cred, &rv);
376-
let req = logger.checkpoint().unwrap();
389+
let req = logger.checkpoint();
377390
// find the client entry in the built request
378391
let crec = req
379392
.client_resolve_info
@@ -463,7 +476,7 @@ mod tests {
463476
let cred = "clients/test/clientCredentials/test";
464477
let rv = [];
465478
logger.log_resolve("id", &ctx, cred, &rv);
466-
let req = logger.checkpoint().unwrap();
479+
let req = logger.checkpoint();
467480
let crec = req
468481
.client_resolve_info
469482
.iter()
@@ -522,7 +535,7 @@ mod tests {
522535

523536
let cred = "clients/test/clientCredentials/test";
524537
logger.log_resolve("id", &Struct::default(), cred, &rv);
525-
let req = logger.checkpoint().unwrap();
538+
let req = logger.checkpoint();
526539

527540
let flag_info = req
528541
.flag_resolve_info
@@ -593,7 +606,7 @@ mod tests {
593606

594607
let cred = "clients/test/clientCredentials/test";
595608
logger.log_resolve("id", &Struct::default(), cred, &rv);
596-
let req = logger.checkpoint().unwrap();
609+
let req = logger.checkpoint();
597610

598611
let flag_info = req
599612
.flag_resolve_info
@@ -706,17 +719,17 @@ mod tests {
706719
let lg = logger.clone();
707720
let tx_thread = tx.clone();
708721
let chk_handle = thread::spawn(move || {
709-
for _ in 0..3 {
722+
for _ in 0..10 {
710723
thread::sleep(Duration::from_millis(10));
711-
let req = lg.checkpoint().unwrap();
712-
let _ = tx_thread.send(req);
724+
tx_thread.send(lg.checkpoint()).unwrap();
713725
}
714726
});
715727

716728
chk_handle.join().unwrap();
717729
done.store(true, Ordering::Relaxed);
718730
let total_expected = handles.into_iter().map(|h| h.join().unwrap()).sum::<i64>();
719-
let _ = tx.send(logger.checkpoint().unwrap());
731+
// logger.checkpoint().iter().
732+
tx.send(logger.checkpoint()).unwrap();
720733

721734
// Aggregate all checkpoint outputs
722735
let mut sum_variants: i64 = 0;

wasm/go-host/resolver_api.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,9 @@ func (r *ResolverApi) consume(addr uint32) []byte {
269269
length := binary.LittleEndian.Uint32(lenBytes) - 4
270270

271271
// Read data
272-
data, _ := memory.Read(addr, length)
272+
view, _ := memory.Read(addr, length)
273+
// make a copy before freeing
274+
data := append([]byte(nil), view...)
273275

274276
// Free memory
275277
ctx := context.Background()

wasm/node-host/src/wasm-msg.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ export class ApiBuilder<T = {}> {
122122

123123
private consume<T>(ptr:number, codec:Codec<T>): T {
124124
const data = this.viewBuffer(ptr);
125-
const res = codec.decode(data);
125+
const res = codec.decode(data.slice());
126126
this.free(ptr);
127127
return res;
128128
}

wasm/rust-guest/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ prost = { version = "0.12", default-features = false }
1515
prost-types = { version = "0.12", default-features = false }
1616
# TODO re-export Bytes
1717
bytes = { version = "1.4.0", default-features = false }
18-
wee_alloc = "0.4"
1918
arc-swap = "1.7.1"
2019

2120
[build-dependencies]

wasm/rust-guest/src/lib.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@ use arc_swap::ArcSwapOption;
66
use bytes::Bytes;
77
use prost::Message;
88

9-
#[global_allocator]
10-
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
11-
129
use confidence_resolver::proto::confidence::flags::resolver::v1::{
1310
LogMessage, ResolveWithStickyRequest, WriteFlagLogsRequest,
1411
};
@@ -146,7 +143,7 @@ impl Host for WasmHost {
146143
client: &Client,
147144
sdk: &Option<Sdk>,
148145
) {
149-
let _ = LOGGER.log_resolve(
146+
LOGGER.log_resolve(
150147
resolve_id,
151148
evaluation_context,
152149
&client.client_credential_name,
@@ -161,7 +158,7 @@ impl Host for WasmHost {
161158
client: &Client,
162159
sdk: &Option<Sdk>,
163160
) {
164-
let _ = LOGGER.log_assigns(resolve_id, evaluation_context, assigned_flags, client, sdk);
161+
LOGGER.log_assigns(resolve_id, evaluation_context, assigned_flags, client, sdk);
165162
}
166163

167164
fn encrypt_resolve_token(token_data: &[u8], _encryption_key: &[u8]) -> Result<Vec<u8>, String> {
@@ -215,7 +212,7 @@ wasm_msg_guest! {
215212
Ok((&resolve_result.resolved_value).into())
216213
}
217214
fn flush_logs(_request:Void) -> WasmResult<WriteFlagLogsRequest> {
218-
LOGGER.checkpoint().map_err(|e| e.into())
215+
Ok(LOGGER.checkpoint())
219216
}
220217

221218
}

0 commit comments

Comments
 (0)