space-data-module-sdk 0.8.19 → 0.8.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +2 -2
  3. package/docs/assembly.js +190 -0
  4. package/docs/browser-wasmedge-isomorphic.html +6 -1
  5. package/docs/byo-wasm-quickstart.html +6 -1
  6. package/docs/conformance.html +6 -1
  7. package/docs/events-abi.html +6 -1
  8. package/docs/families/analytics.html +7 -2
  9. package/docs/families/attitude.html +7 -2
  10. package/docs/families/behavior.html +7 -2
  11. package/docs/families/breakup.html +7 -2
  12. package/docs/families/conjunction.html +7 -2
  13. package/docs/families/data-source.html +7 -2
  14. package/docs/families/effects.html +7 -2
  15. package/docs/families/environment.html +7 -2
  16. package/docs/families/estimation.html +7 -2
  17. package/docs/families/gnc.html +7 -2
  18. package/docs/families/maneuver.html +7 -2
  19. package/docs/families/obstruction.html +7 -2
  20. package/docs/families/propagator.html +7 -2
  21. package/docs/families/propulsion.html +7 -2
  22. package/docs/families/reentry.html +7 -2
  23. package/docs/families/rf.html +7 -2
  24. package/docs/families/scheduler.html +7 -2
  25. package/docs/families/sensor.html +7 -2
  26. package/docs/families/signature.html +7 -2
  27. package/docs/flatsql-host-contract.html +7 -2
  28. package/docs/flatsql-host-contract.md +2 -2
  29. package/docs/flatsql-streaming-standard.html +6 -1
  30. package/docs/gpu-module-abi.html +6 -1
  31. package/docs/index.html +21 -14
  32. package/docs/isomorphic-pthreads.html +19 -2
  33. package/docs/isomorphic-pthreads.md +59 -1
  34. package/docs/isomorphic-sdn-runtime-plan.html +6 -1
  35. package/docs/language-runtime-matrix.html +6 -1
  36. package/docs/llms.txt +1 -1
  37. package/docs/module-bundle-runtime-plan.html +6 -1
  38. package/docs/module-publication-standard.html +6 -1
  39. package/docs/node-red-default-node-parity.html +6 -1
  40. package/docs/protect-and-sign.html +6 -1
  41. package/docs/protocol-installation.html +6 -1
  42. package/docs/publication-submission.html +6 -1
  43. package/docs/secrets-capability.html +6 -1
  44. package/docs/styles.css +32 -0
  45. package/docs/testing-harness.html +6 -1
  46. package/docs/tri-runtime-parity-gate.html +6 -1
  47. package/docs/tri-runtime-parity.html +6 -1
  48. package/package.json +1 -1
  49. package/src/host/browserModuleHarness.js +39 -35
  50. package/src/host/isomorphicLoader.js +13 -6
  51. package/src/host/wasiShim.js +57 -13
  52. package/src/host/wasiThreadBrowserWorker.mjs +1 -0
  53. package/src/host/wasiThreadHost.js +16 -1
  54. package/src/host/wasiThreadWorker.mjs +2 -1
  55. package/src/host/wasiThreadWorkerRuntime.js +2 -1
  56. package/src/testing/browserModuleHarness.js +2 -0
  57. package/src/testing/buildWasmEdgeRunner.js +147 -4
  58. package/src/testing/docker/wasmedge-parity.Dockerfile +23 -1
  59. package/src/testing/isomorphicHarness.js +5 -0
  60. package/src/testing/native/wasmedge-0.16.4-atomic-wait.patch +111 -0
  61. package/src/testing/native/wasmedge_wasi_threads_runner.c +345 -0
  62. package/src/testing/parityBrowserRunner.js +12 -4
  63. package/src/testing/parityHarness.js +11 -3
  64. package/src/testing/parityLanes.js +29 -5
@@ -5,6 +5,8 @@ import path from "node:path";
5
5
  import process from "node:process";
6
6
  import { promisify } from "node:util";
7
7
  import { fileURLToPath } from "node:url";
8
+ import { createHash } from "node:crypto";
9
+ import { mkdir, readFile, writeFile, copyFile, rename, rm } from "node:fs/promises";
8
10
 
9
11
  const execFileAsync = promisify(execFile);
10
12
  const __filename = fileURLToPath(import.meta.url);
@@ -56,11 +58,13 @@ function resolveGeneratedIncludeDir(requestedIncludeDir) {
56
58
  );
57
59
  }
58
60
 
59
- export function resolveWasmEdgeRunnerSourcePath() {
61
+ export function resolveWasmEdgeRunnerSourcePath(options = {}) {
60
62
  return path.resolve(
61
63
  __dirname,
62
64
  "native",
63
- "wasmedge_emscripten_pthread_runner.c",
65
+ options.runnerKind === "wasi-threads"
66
+ ? "wasmedge_wasi_threads_runner.c"
67
+ : "wasmedge_emscripten_pthread_runner.c",
64
68
  );
65
69
  }
66
70
 
@@ -127,7 +131,7 @@ export function resolveWasmEdgeRunnerBuildPlan(options = {}) {
127
131
  throw new Error("Missing runner outputPath.");
128
132
  }
129
133
 
130
- const runnerSourcePath = resolveWasmEdgeRunnerSourcePath();
134
+ const runnerSourcePath = resolveWasmEdgeRunnerSourcePath(options);
131
135
  const wasmedgeSharedLibraryPath = path.join(
132
136
  wasmedgeLibDir,
133
137
  resolveWasmEdgeSharedLibraryFilename(),
@@ -146,6 +150,7 @@ export function resolveWasmEdgeRunnerBuildPlan(options = {}) {
146
150
  process.platform === "darwin"
147
151
  ? [
148
152
  "clang",
153
+ "-Wl,-headerpad_max_install_names",
149
154
  runnerSourcePath,
150
155
  "-std=c11",
151
156
  "-O2",
@@ -191,6 +196,7 @@ export async function buildWasmEdgeEmscriptenPthreadRunner(options = {}) {
191
196
  process.platform === "darwin"
192
197
  ? [
193
198
  "clang",
199
+ "-Wl,-headerpad_max_install_names",
194
200
  basePlan.runnerSourcePath,
195
201
  "-std=c11",
196
202
  "-O2",
@@ -242,12 +248,149 @@ export async function buildWasmEdgeEmscriptenPthreadRunner(options = {}) {
242
248
  cwd: options.cwd ?? process.cwd(),
243
249
  });
244
250
  if (process.platform === "darwin") {
251
+ let linkedLibrary = plan.wasmedgeSharedLibraryPath;
252
+ if (options.runnerKind === "wasi-threads") {
253
+ // DYLD_LIBRARY_PATH can override even an absolute install name by its
254
+ // basename. Give this exact runtime a unique name so ambient SDK paths
255
+ // cannot silently replace the corrected atomic-wait implementation.
256
+ const sha = createHash("sha256").update(await readFile(linkedLibrary)).digest("hex");
257
+ linkedLibrary = path.join(path.dirname(plan.outputPath), `libsdm-wasmedge-${sha}.dylib`);
258
+ if (!existsSync(linkedLibrary)) {
259
+ const stagingLibrary = `${linkedLibrary}.${process.pid}`;
260
+ try {
261
+ await copyFile(plan.wasmedgeSharedLibraryPath, stagingLibrary);
262
+ await rename(stagingLibrary, linkedLibrary);
263
+ } finally { await rm(stagingLibrary, { force: true }); }
264
+ }
265
+ }
245
266
  await execFileAsync("install_name_tool", [
246
267
  "-change",
247
268
  "@rpath/libwasmedge.0.dylib",
248
- plan.wasmedgeSharedLibraryPath,
269
+ linkedLibrary,
249
270
  plan.outputPath,
250
271
  ]);
251
272
  }
252
273
  return plan.outputPath;
253
274
  }
275
+
276
+ const WASMEDGE_THREADS_REVISION = "be85c2fbba68318f103b4a766728f6946e65abf8";
277
+ const runtimeBuilds = new Map();
278
+
279
+ // Upstream 0.16.4 can lose atomic notifications between comparison and sleep.
280
+ // Keep its established SDN fix isolated from the owner's installed SDK/CLI.
281
+ // An explicit include/lib pair remains supported for operator-built runtimes.
282
+ async function prepareWasmEdgeThreadsRuntime(options) {
283
+ if ((options.wasmedgeIncludeDir ?? process.env.WASMEDGE_INCLUDE_DIR) &&
284
+ (options.wasmedgeLibDir ?? process.env.WASMEDGE_LIB_DIR)) return {};
285
+ const patchPath = path.join(__dirname, "native/wasmedge-0.16.4-atomic-wait.patch");
286
+ const digest = createHash("sha256").update(WASMEDGE_THREADS_REVISION)
287
+ .update(await readFile(patchPath)).update(`${process.platform}-${process.arch}`).digest("hex");
288
+ if (!runtimeBuilds.has(digest)) {
289
+ const build = (async () => {
290
+ const root = path.join(os.tmpdir(), "sdm-wasmedge-runtimes", digest);
291
+ const prefix = path.join(root, "sdk");
292
+ const marker = path.join(root, "complete.json");
293
+ const lock = `${root}.lock`;
294
+ await mkdir(path.dirname(root), { recursive: true, mode: 0o700 });
295
+ const started = Date.now();
296
+ let held = false;
297
+ while (!existsSync(marker)) {
298
+ try {
299
+ await mkdir(lock);
300
+ held = true;
301
+ await writeFile(path.join(lock, "pid"), String(process.pid));
302
+ break;
303
+ } catch (error) {
304
+ if (error.code !== "EEXIST") throw error;
305
+ try {
306
+ const owner = Number(await readFile(path.join(lock, "pid"), "utf8"));
307
+ if (owner > 0) {
308
+ try { process.kill(owner, 0); }
309
+ catch (error) { if (error.code === "ESRCH") await rm(lock, { recursive: true, force: true }); }
310
+ }
311
+ } catch { /* another builder is still acquiring its lock */ }
312
+ if (Date.now() - started > 900000) throw new Error("Timed out waiting for the isolated WasmEdge runtime build.");
313
+ await new Promise((resolve) => setTimeout(resolve, 250));
314
+ }
315
+ }
316
+ try {
317
+ if (!existsSync(marker)) {
318
+ await mkdir(root, { recursive: true });
319
+ const source = path.join(root, "source");
320
+ const buildDir = path.join(root, "build");
321
+ const run = (command, args) => execFileAsync(command, args, { timeout: 900000, maxBuffer: 16 * 1024 * 1024 });
322
+ if (!existsSync(path.join(source, ".git"))) {
323
+ await run("git", ["clone", "--depth", "1", "--branch", "0.16.4", "https://github.com/WasmEdge/WasmEdge.git", source]);
324
+ }
325
+ const revision = (await run("git", ["-C", source, "rev-parse", "HEAD"])).stdout.trim();
326
+ if (revision !== WASMEDGE_THREADS_REVISION) throw new Error(`Unexpected WasmEdge source revision: ${revision}`);
327
+ try { await run("git", ["-C", source, "apply", "--reverse", "--check", patchPath]); }
328
+ catch { await run("git", ["-C", source, "apply", patchPath]); }
329
+ await run("cmake", ["-S", source, "-B", buildDir, "-G", "Ninja",
330
+ "-DCMAKE_BUILD_TYPE=Release", `-DCMAKE_INSTALL_PREFIX=${prefix}`,
331
+ process.platform === "darwin" ? "-DCMAKE_CXX_FLAGS=-Wno-invalid-specialization" : "-DCMAKE_CXX_FLAGS=-Wno-error=maybe-uninitialized -Wno-error=array-bounds",
332
+ "-DWASMEDGE_USE_LLVM=OFF",
333
+ "-DWASMEDGE_BUILD_PLUGINS=OFF", "-DWASMEDGE_BUILD_TOOLS=OFF", "-DWASMEDGE_FORCE_DISABLE_LTO=ON"]);
334
+ await run("cmake", ["--build", buildDir, "-j", "4"]);
335
+ await run("cmake", ["--install", buildDir]);
336
+ await writeFile(marker, JSON.stringify({ revision, patchSha256: createHash("sha256").update(await readFile(patchPath)).digest("hex") }));
337
+ }
338
+ } finally {
339
+ if (held) await rm(lock, { recursive: true, force: true });
340
+ }
341
+ const libDir = existsSync(path.join(prefix, "lib", resolveWasmEdgeSharedLibraryFilename())) ? "lib" : "lib64";
342
+ return { wasmedgeIncludeDir: path.join(prefix, "include"), wasmedgeLibDir: path.join(prefix, libDir) };
343
+ })();
344
+ runtimeBuilds.set(digest, build);
345
+ build.catch(() => runtimeBuilds.delete(digest));
346
+ }
347
+ return runtimeBuilds.get(digest);
348
+ }
349
+
350
+ export async function buildWasmEdgeWasiThreadsRunner(options = {}) {
351
+ const runtime = await prepareWasmEdgeThreadsRuntime(options);
352
+ return buildWasmEdgeEmscriptenPthreadRunner({ ...options, ...runtime, runnerKind: "wasi-threads" });
353
+ }
354
+
355
+ const builds = new Map();
356
+
357
+ // The command runner is separate from the legacy resident Emscripten host.
358
+ // Invalidate cached binaries when either the source or linked library changes.
359
+ export async function resolveWasmEdgeWasiThreadsRunner(options = {}) {
360
+ const explicit = options.wasmEdgeRunnerBinary ?? options.wasmedgeRunnerBinary ??
361
+ process.env.SDM_WASMEDGE_RUNNER_BINARY;
362
+ if (explicit) return path.resolve(String(explicit));
363
+ options = { ...options, ...await prepareWasmEdgeThreadsRuntime(options) };
364
+ const plan = resolveWasmEdgeRunnerBuildPlan({
365
+ ...options, runnerKind: "wasi-threads", outputPath: path.join(os.tmpdir(), "sdm-runner"),
366
+ });
367
+ const digest = createHash("sha256")
368
+ .update(await readFile(__filename))
369
+ .update(await readFile(plan.runnerSourcePath))
370
+ .update(await readFile(plan.wasmedgeSharedLibraryPath))
371
+ .update(JSON.stringify([process.platform, process.arch, plan.wasmedgeIncludeDir,
372
+ plan.wasmedgeSharedLibraryPath, plan.compilerCommand, plan.compilerArgs]))
373
+ .digest("hex");
374
+ if (!builds.has(digest)) {
375
+ const build = (async () => {
376
+ const dir = path.join(os.tmpdir(), "sdm-wasmedge-runners");
377
+ await mkdir(dir, { recursive: true, mode: 0o700 });
378
+ const outputPath = path.join(dir, `wasi-threads-${digest}`);
379
+ if (!existsSync(outputPath)) {
380
+ const staging = `${outputPath}.${process.pid}`;
381
+ try {
382
+ await buildWasmEdgeWasiThreadsRunner({ ...options, outputPath: staging });
383
+ await rename(staging, outputPath);
384
+ } finally {
385
+ await rm(staging, { force: true });
386
+ }
387
+ }
388
+ // This executes the linked library's version check, including on cache hits.
389
+ await execFileAsync(outputPath, ["--version"]);
390
+ return outputPath;
391
+ })();
392
+ builds.set(digest, build);
393
+ build.catch(() => builds.delete(digest));
394
+ }
395
+ return builds.get(digest);
396
+ }
@@ -11,7 +11,7 @@ ARG WASMEDGE_VERSION
11
11
  RUN test -n "${WASMEDGE_VERSION}" || (echo "WASMEDGE_VERSION build-arg is required (injected from src/testing/wasmedgePin.json)" && exit 1)
12
12
 
13
13
  RUN apt-get update \
14
- && apt-get install -y --no-install-recommends curl ca-certificates \
14
+ && apt-get install -y --no-install-recommends curl ca-certificates build-essential git cmake ninja-build \
15
15
  && rm -rf /var/lib/apt/lists/*
16
16
 
17
17
  RUN set -eux; \
@@ -34,6 +34,28 @@ RUN set -eux; \
34
34
  ENV PATH="/opt/wasmedge/bin:${PATH}" \
35
35
  LD_LIBRARY_PATH="/opt/wasmedge/lib64:/opt/wasmedge/lib"
36
36
 
37
+ COPY native/wasmedge_wasi_threads_runner.c /tmp/sdm-wasi-threads-runner.c
38
+ COPY native/wasmedge-0.16.4-atomic-wait.patch /tmp/atomic-wait.patch
39
+ # The ordinary CLI keeps its release library. Only the SDK thread runner uses
40
+ # the isolated 0.16.4 atomic-wait correction, matching the native build.
41
+ RUN git clone --depth 1 --branch 0.16.4 https://github.com/WasmEdge/WasmEdge.git /tmp/wasmedge-source \
42
+ && test "$(git -C /tmp/wasmedge-source rev-parse HEAD)" = be85c2fbba68318f103b4a766728f6946e65abf8 \
43
+ && git -C /tmp/wasmedge-source apply /tmp/atomic-wait.patch \
44
+ && cmake -S /tmp/wasmedge-source -B /tmp/wasmedge-build -G Ninja \
45
+ -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/opt/sdm-wasmedge \
46
+ "-DCMAKE_CXX_FLAGS=-Wno-error=maybe-uninitialized -Wno-error=array-bounds" \
47
+ -DWASMEDGE_USE_LLVM=OFF -DWASMEDGE_BUILD_PLUGINS=OFF \
48
+ -DWASMEDGE_BUILD_TOOLS=OFF -DWASMEDGE_FORCE_DISABLE_LTO=ON \
49
+ && cmake --build /tmp/wasmedge-build -j 4 \
50
+ && cmake --install /tmp/wasmedge-build \
51
+ && rm -rf /tmp/wasmedge-source /tmp/wasmedge-build /tmp/atomic-wait.patch
52
+ RUN cc /tmp/sdm-wasi-threads-runner.c -std=c11 -O2 -pthread -Wall -Wextra -Werror \
53
+ -I/opt/sdm-wasmedge/include -L/opt/sdm-wasmedge/lib64 -L/opt/sdm-wasmedge/lib -lwasmedge \
54
+ -Wl,--disable-new-dtags,-rpath,/opt/sdm-wasmedge/lib64,-rpath,/opt/sdm-wasmedge/lib \
55
+ -o /opt/wasmedge/bin/sdm-wasi-threads-runner \
56
+ && /opt/wasmedge/bin/sdm-wasi-threads-runner --version \
57
+ && rm /tmp/sdm-wasi-threads-runner.c
58
+
37
59
  # The parity harness always runs this image with an explicit wasmedge argv
38
60
  # (flags + guest module + guest args) so the container is a pure runtime shell.
39
61
  ENTRYPOINT ["wasmedge"]
@@ -58,6 +58,10 @@ export async function createStandaloneHarness(runtimeKind, wasmPath, options = {
58
58
  allowRawInvoke: options.allowRawInvoke,
59
59
  initialMemoryBytes: options.initialMemoryBytes,
60
60
  maximumMemoryBytes: options.maximumMemoryBytes,
61
+ maxThreads: options.maxThreads,
62
+ enableBrowserWasiThreads: options.enableBrowserWasiThreads,
63
+ wasiThreadWorkerUrl: options.wasiThreadWorkerUrl,
64
+ wasiThreadWorkerBaseUrl: options.wasiThreadWorkerBaseUrl,
61
65
  });
62
66
  }
63
67
 
@@ -65,6 +69,7 @@ export async function createStandaloneHarness(runtimeKind, wasmPath, options = {
65
69
  return loadModule({
66
70
  wasmSource: resolvedWasmPath,
67
71
  runtimeKind: "wasmedge",
72
+ surface: options.surface ?? "command",
68
73
  enableThreads: options.enableThreads ?? false,
69
74
  wasmEdgeBinary: options.wasmEdgeBinary,
70
75
  wasmEdgeRunnerBinary: options.wasmEdgeRunnerBinary,
@@ -0,0 +1,111 @@
1
+ diff --git a/include/executor/engine/atomic.ipp b/include/executor/engine/atomic.ipp
2
+ index 8568e24..8414705 100644
3
+ --- a/include/executor/engine/atomic.ipp
4
+ +++ b/include/executor/engine/atomic.ipp
5
+ @@ -478,40 +478,32 @@ Executor::atomicWait(Runtime::Instance::MemoryInstance &MemInst,
6
+ auto *AtomicObj = MemInst.getPointer<std::atomic<T> *>(Address);
7
+ assuming(AtomicObj);
8
+
9
+ + // Compare, register, and sleep under the notifier's mutex. Otherwise a
10
+ + // store+notify between the comparison and wait can be lost forever.
11
+ + std::unique_lock<decltype(WaiterMapMutex)> Locker(WaiterMapMutex);
12
+ if (AtomicObj->load() != Expected.le()) {
13
+ return UINT32_C(1); // NotEqual
14
+ }
15
+ -
16
+ - decltype(WaiterMap)::iterator WaiterIterator;
17
+ - {
18
+ - std::unique_lock<decltype(WaiterMapMutex)> Locker(WaiterMapMutex);
19
+ - WaiterIterator = WaiterMap.emplace(Address, &MemInst);
20
+ - }
21
+ -
22
+ + auto WaiterIterator = WaiterMap.emplace(Address, &MemInst);
23
+ cxx20::scope_exit ScopeExitHolder([&]() noexcept {
24
+ - std::unique_lock<decltype(WaiterMapMutex)> Locker(WaiterMapMutex);
25
+ WaiterMap.erase(WaiterIterator);
26
+ });
27
+ -
28
+ - while (true) {
29
+ - std::unique_lock<decltype(WaiterIterator->second.Mutex)> Locker(
30
+ - WaiterIterator->second.Mutex);
31
+ - std::cv_status WaitResult = std::cv_status::no_timeout;
32
+ - if (!Until) {
33
+ - WaiterIterator->second.Cond.wait(Locker);
34
+ - } else {
35
+ - WaitResult = WaiterIterator->second.Cond.wait_until(Locker, *Until);
36
+ - }
37
+ - if (unlikely(StopToken.load(std::memory_order_relaxed) != 0)) {
38
+ - return Unexpect(ErrCode::Value::Interrupted);
39
+ - }
40
+ - if (likely(AtomicObj->load() != Expected.le())) {
41
+ - return UINT32_C(0); // ok
42
+ - }
43
+ - if (WaitResult == std::cv_status::timeout) {
44
+ - return UINT32_C(2); // Timed-out
45
+ - }
46
+ + auto Ready = [&]() {
47
+ + return WaiterIterator->second.Notified ||
48
+ + StopToken.load(std::memory_order_relaxed) != 0;
49
+ + };
50
+ + bool Woken = true;
51
+ + if (!Until) {
52
+ + WaiterIterator->second.Cond.wait(Locker, Ready);
53
+ + } else {
54
+ + Woken = WaiterIterator->second.Cond.wait_until(Locker, *Until, Ready);
55
+ + }
56
+ + if (unlikely(StopToken.load(std::memory_order_relaxed) != 0)) {
57
+ + return Unexpect(ErrCode::Value::Interrupted);
58
+ }
59
+ + // A notification succeeds even when the memory value has not changed.
60
+ + return Woken ? UINT32_C(0) : UINT32_C(2);
61
+ +
62
+ }
63
+
64
+ } // namespace Executor
65
+ diff --git a/include/executor/executor.h b/include/executor/executor.h
66
+ index fbcc6ac..6683ee9 100644
67
+ --- a/include/executor/executor.h
68
+ +++ b/include/executor/executor.h
69
+ @@ -28,6 +28,7 @@
70
+ #include "runtime/stackmgr.h"
71
+ #include "runtime/storemgr.h"
72
+
73
+ +#include <map>
74
+ #include <atomic>
75
+ #include <condition_variable>
76
+ #include <csignal>
77
+ @@ -1103,7 +1104,7 @@ private:
78
+
79
+ /// Waiter struct for atomic instructions
80
+ struct Waiter {
81
+ - std::mutex Mutex;
82
+ + bool Notified = false;
83
+ std::condition_variable Cond;
84
+ Runtime::Instance::MemoryInstance *MemInst;
85
+ Waiter(Runtime::Instance::MemoryInstance *Inst) noexcept : MemInst(Inst) {}
86
+ @@ -1111,7 +1112,7 @@ private:
87
+ /// Waiter map mutex
88
+ std::mutex WaiterMapMutex;
89
+ /// Waiter multimap
90
+ - std::unordered_multimap<uint32_t, Waiter> WaiterMap;
91
+ + std::multimap<uint32_t, Waiter> WaiterMap;
92
+
93
+ /// WasmEdge configuration
94
+ const Configure Conf;
95
+ diff --git a/lib/executor/engine/threadInstr.cpp b/lib/executor/engine/threadInstr.cpp
96
+ index 0d1ca70..5331e95 100644
97
+ --- a/lib/executor/engine/threadInstr.cpp
98
+ +++ b/lib/executor/engine/threadInstr.cpp
99
+ @@ -67,8 +67,10 @@ Executor::atomicNotify(Runtime::Instance::MemoryInstance &MemInst,
100
+ auto Range = WaiterMap.equal_range(Address);
101
+ for (auto Iterator = Range.first; Total < Count && Iterator != Range.second;
102
+ ++Iterator) {
103
+ - if (likely(&MemInst == Iterator->second.MemInst)) {
104
+ - Iterator->second.Cond.notify_all();
105
+ + if (likely(&MemInst == Iterator->second.MemInst) &&
106
+ + !Iterator->second.Notified) {
107
+ + Iterator->second.Notified = true;
108
+ + Iterator->second.Cond.notify_one();
109
+ ++Total;
110
+ }
111
+ }