summaryrefslogtreecommitdiff
path: root/test_ffi/tests/ffi_callback_errors.ts
blob: dda4de5fbd9fcd826e7e2ea3ef74cda52f060b46 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.

const targetDir = Deno.execPath().replace(/[^\/\\]+$/, "");
const [libPrefix, libSuffix] = {
  darwin: ["lib", "dylib"],
  linux: ["lib", "so"],
  windows: ["", "dll"],
}[Deno.build.os];
const libPath = `${targetDir}/${libPrefix}test_ffi.${libSuffix}`;

const dylib = Deno.dlopen(
  libPath,
  {
    store_function_2: {
      parameters: ["function"],
      result: "void",
    },
    call_stored_function_2: {
      parameters: ["u8"],
      result: "void",
    },
    call_stored_function_2_from_other_thread: {
      name: "call_stored_function_2",
      parameters: ["u8"],
      result: "void",
      nonblocking: true,
    },
    call_stored_function_2_thread_safe: {
      parameters: ["u8"],
      result: "void",
    },
  } as const,
);

globalThis.addEventListener("error", (data) => {
  console.log("Unhandled error");
  data.preventDefault();
});
globalThis.onerror = (data) => {
  console.log("Unhandled error");
  if (typeof data !== "string") {
    data.preventDefault();
  }
};

globalThis.addEventListener("unhandledrejection", (data) => {
  console.log("Unhandled rejection");
  data.preventDefault();
});

const timer = setTimeout(() => {
  console.error(
    "Test failed, final callback did not get picked up by Deno event loop",
  );
  Deno.exit(-1);
}, 5_000);

Deno.unrefTimer(timer);

enum CallCase {
  SyncSelf,
  SyncFfi,
  AsyncSelf,
  AsyncSyncFfi,
  AsyncFfi,
}
type U8CallCase = Deno.NativeU8Enum<CallCase>;

const throwCb = (c: CallCase): number => {
  console.log("CallCase:", CallCase[c]);
  if (c === CallCase.AsyncFfi) {
    cb.unref();
  }
  throw new Error("Error");
};

const THROW_CB_DEFINITION = {
  parameters: ["u8" as U8CallCase],
  result: "u8",
} as const;

const cb = new Deno.UnsafeCallback(THROW_CB_DEFINITION, throwCb);

try {
  const fnPointer = new Deno.UnsafeFnPointer(cb.pointer, THROW_CB_DEFINITION);

  fnPointer.call(CallCase.SyncSelf);
} catch (_err) {
  console.log(
    "Throwing errors from an UnsafeCallback called from a synchronous UnsafeFnPointer works. Terribly excellent.",
  );
}

dylib.symbols.store_function_2(cb.pointer);
try {
  dylib.symbols.call_stored_function_2(CallCase.SyncFfi);
} catch (_err) {
  console.log(
    "Throwing errors from an UnsafeCallback called from a synchronous FFI symbol works. Terribly excellent.",
  );
}

try {
  const fnPointer = new Deno.UnsafeFnPointer(cb.pointer, {
    ...THROW_CB_DEFINITION,
    nonblocking: true,
  });
  await fnPointer.call(CallCase.AsyncSelf);
} catch (err) {
  throw new Error(
    "Nonblocking UnsafeFnPointer should not be threading through a JS error thrown on the other side of the call",
    {
      cause: err,
    },
  );
}

try {
  await dylib.symbols.call_stored_function_2_from_other_thread(
    CallCase.AsyncSyncFfi,
  );
} catch (err) {
  throw new Error(
    "Nonblocking symbol call should not be threading through a JS error thrown on the other side of the call",
    {
      cause: err,
    },
  );
}
try {
  // Ref the callback to make sure we do not exit before the call is done.
  cb.ref();
  dylib.symbols.call_stored_function_2_thread_safe(CallCase.AsyncFfi);
} catch (err) {
  throw new Error(
    "Blocking symbol call should not be travelling 1.5 seconds forward in time to figure out that it call will trigger a JS error to be thrown",
    {
      cause: err,
    },
  );
}