Skip to content

Commit e3f9022

Browse files
committed
feat: add Linux cooperative run-loop drain via g_main_context_iteration
1 parent caa3774 commit e3f9022

4 files changed

Lines changed: 107 additions & 0 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { dlopen, FFIType } from 'bun:ffi';
2+
import { UnsupportedPlatformError } from '../../../common/errors';
3+
import { currentPlatform } from '../../../common/platform';
4+
5+
/**
6+
* Loads GLib's main-context iteration symbols.
7+
*
8+
* GLib (not GTK directly) owns the main-loop primitives Sambar uses to pump the
9+
* Linux UI cooperatively, mirroring the macOS CoreFoundation pump (D020).
10+
* `libglib-2.0` is a hard dependency of GTK 4, so it is always present wherever
11+
* `libgtk-4` is.
12+
*
13+
* `g_main_context_iteration(context, may_block)` dispatches at most one set of
14+
* ready sources; a `NULL` context means the default one. `g_main_context_pending`
15+
* reports whether any sources are ready, letting us drain to quiescence without
16+
* ever blocking Bun's thread.
17+
*
18+
* Only callable on Linux — throws {@link UnsupportedPlatformError} otherwise so
19+
* the module stays safely importable on macOS for unit testing.
20+
*/
21+
22+
const LIBGLIB_PATH = 'libglib-2.0.so.0';
23+
24+
export const loadGlibFFI = () => {
25+
const platform = currentPlatform();
26+
if (platform !== 'linux') {
27+
throw new UnsupportedPlatformError(
28+
`loadGlibFFI() is only supported on Linux; current platform is ${platform}`,
29+
);
30+
}
31+
return dlopen(LIBGLIB_PATH, {
32+
g_main_context_iteration: {
33+
args: [FFIType.pointer, FFIType.i32],
34+
returns: FFIType.i32,
35+
},
36+
g_main_context_pending: {
37+
args: [FFIType.pointer],
38+
returns: FFIType.i32,
39+
},
40+
});
41+
};
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { loadGlibFFI } from './glib-ffi';
2+
3+
/**
4+
* Linux native run-loop drain.
5+
*
6+
* The Linux analogue of the macOS CF drain (D020): the {@link CooperativePump}
7+
* calls this each tick to service GTK/GLib events without blocking Bun's thread.
8+
* We iterate the default main context while it reports pending sources, never
9+
* blocking (`may_block = FALSE`), bounded by a per-tick budget so a busy loop
10+
* cannot starve Bun's own event loop.
11+
*/
12+
13+
const NULL_CONTEXT = null;
14+
const MAY_BLOCK_FALSE = 0;
15+
16+
/** Upper bound on inner iterations per tick. */
17+
const DRAIN_BUDGET = 256;
18+
19+
/**
20+
* Create the Linux drain function. Throws {@link UnsupportedPlatformError} off
21+
* Linux (via the loader). Cheap to call repeatedly; never blocks.
22+
*/
23+
export const createLinuxDrain = (): (() => void) => {
24+
const glib = loadGlibFFI();
25+
return () => {
26+
for (let i = 0; i < DRAIN_BUDGET; i += 1) {
27+
if (glib.symbols.g_main_context_pending(NULL_CONTEXT) === 0) {
28+
break;
29+
}
30+
glib.symbols.g_main_context_iteration(NULL_CONTEXT, MAY_BLOCK_FALSE);
31+
}
32+
};
33+
};
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { describe, expect, test } from 'bun:test';
2+
import { currentPlatform } from '../../../src/common/platform';
3+
import { createLinuxDrain } from '../../../src/main/platform/linux/gtk-run-loop';
4+
5+
if (currentPlatform() === 'linux') {
6+
describe('createLinuxDrain', () => {
7+
test('returns a drain that runs many times without crashing', () => {
8+
const drain = createLinuxDrain();
9+
for (let i = 0; i < 50; i += 1) {
10+
drain();
11+
}
12+
expect(typeof drain).toBe('function');
13+
});
14+
});
15+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { describe, expect, test } from 'bun:test';
2+
import { UnsupportedPlatformError } from '../../../../../src/common/errors';
3+
import { currentPlatform } from '../../../../../src/common/platform';
4+
import { createLinuxDrain } from '../../../../../src/main/platform/linux/gtk-run-loop';
5+
6+
describe('createLinuxDrain export', () => {
7+
test('is a function', () => {
8+
expect(typeof createLinuxDrain).toBe('function');
9+
});
10+
});
11+
12+
if (currentPlatform() !== 'linux') {
13+
describe('createLinuxDrain on non-Linux hosts', () => {
14+
test('throws UnsupportedPlatformError', () => {
15+
expect(() => createLinuxDrain()).toThrow(UnsupportedPlatformError);
16+
});
17+
});
18+
}

0 commit comments

Comments
 (0)