Skip to content

Commit b50aefd

Browse files
authored
ref(profiling) Fix electron crash (#14216)
This PR fixes the electron crash observed in #13978 I am not entirely sure as to why this causes a sigabrt, so I am working around the issue (obtaining a coredump out of electron did not seem trivial and my knowledge around electron isn't very extensive). The v8 options class and the constants are exposed correctly, I ruled that out, however the crash still seems to happen when they are used in this specific signature. In order to have this running with electron, users will require to use the electron/rebuild package, which is the recommended approach by electron that rebuilds native node addons by providing the correct abi headers for the electron version the user is running. For now, we wont provide any prebuilt binaries and instead rely on the fallback mechanism to load the correct module. I will reevaluate this if it causes issues with bundling and look to add proper runtime electron detection.
1 parent a55e2b0 commit b50aefd

File tree

7 files changed

+114
-8
lines changed

7 files changed

+114
-8
lines changed

.github/workflows/build.yml

+19-2
Original file line numberDiff line numberDiff line change
@@ -1235,7 +1235,7 @@ jobs:
12351235
(needs.job_get_metadata.outputs.is_release == 'true')
12361236
)
12371237
needs: [job_get_metadata, job_build, job_e2e_prepare]
1238-
runs-on: ubuntu-20.04
1238+
runs-on: ubuntu-22.04
12391239
timeout-minutes: 15
12401240
env:
12411241
E2E_TEST_AUTH_TOKEN: ${{ secrets.E2E_TEST_AUTH_TOKEN }}
@@ -1255,19 +1255,24 @@ jobs:
12551255
uses: actions/checkout@v4
12561256
with:
12571257
ref: ${{ env.HEAD_COMMIT }}
1258+
12581259
- uses: pnpm/action-setup@v4
12591260
with:
12601261
version: 9.4.0
1262+
12611263
- name: Set up Node
12621264
uses: actions/setup-node@v4
12631265
with:
12641266
node-version: 22
1267+
12651268
- name: Restore caches
12661269
uses: ./.github/actions/restore-cache
12671270
with:
12681271
dependency_cache_key: ${{ needs.job_build.outputs.dependency_cache_key }}
1272+
12691273
- name: Build Profiling Node
12701274
run: yarn lerna run build:lib --scope @sentry/profiling-node
1275+
12711276
- name: Extract Profiling Node Prebuilt Binaries
12721277
uses: actions/download-artifact@v4
12731278
with:
@@ -1306,6 +1311,18 @@ jobs:
13061311
env:
13071312
E2E_TEST_PUBLISH_SCRIPT_NODE_VERSION: ${{ steps.versions.outputs.node }}
13081313

1314+
- name: Setup xvfb and update ubuntu dependencies
1315+
run: |
1316+
sudo apt-get install xvfb x11-xkb-utils xfonts-100dpi xfonts-75dpi xfonts-scalable xfonts-cyrillic x11-apps
1317+
sudo apt-get install build-essential clang libdbus-1-dev libgtk2.0-dev \
1318+
libnotify-dev libgconf2-dev \
1319+
libasound2-dev libcap-dev libcups2-dev libxtst-dev \
1320+
libxss1 libnss3-dev gcc-multilib g++-multilib
1321+
1322+
- name: Install dependencies
1323+
working-directory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }}
1324+
run: yarn install --ignore-engines --frozen-lockfile
1325+
13091326
- name: Build E2E app
13101327
working-directory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }}
13111328
timeout-minutes: 7
@@ -1314,7 +1331,7 @@ jobs:
13141331
- name: Run E2E test
13151332
working-directory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }}
13161333
timeout-minutes: 10
1317-
run: yarn test:assert
1334+
run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:assert
13181335

13191336
job_required_jobs_passed:
13201337
name: All required jobs passed or were skipped
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const process = require('process');
2+
const { test, expect, _electron: electron } = require('@playwright/test');
3+
4+
test('an h1 contains hello world"', async () => {
5+
const electronApp = await electron.launch({
6+
args: ['./index.electron.js'],
7+
process: {
8+
env: {
9+
...process.env,
10+
},
11+
},
12+
});
13+
14+
// Wait for the first BrowserWindow to open
15+
const window = await electronApp.firstWindow();
16+
17+
// Check for the presence of an h1 element with the text "hello"
18+
const headerElement = await window.$('h1');
19+
const headerText = await headerElement.textContent();
20+
expect(headerText).toBe('Hello From Profiled Electron App');
21+
22+
// Close the app
23+
await electronApp.close();
24+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Modules to control application life and create native browser window
2+
const { app, BrowserWindow } = require('electron');
3+
const Sentry = require('@sentry/electron/main');
4+
const path = require('node:path');
5+
6+
Sentry.init({
7+
dsn: 'https://[email protected]/6625302',
8+
debug: true,
9+
tracesSampleRate: 1.0,
10+
});
11+
12+
// Hog the cpu for a second
13+
function block() {
14+
const start = Date.now();
15+
while (start + 1000 > Date.now()) {}
16+
}
17+
18+
const createWindow = () => {
19+
block();
20+
// Create the browser window.
21+
const mainWindow = new BrowserWindow({
22+
width: 800,
23+
height: 600,
24+
webPreferences: {
25+
preload: path.join(__dirname, 'preload.js'),
26+
},
27+
});
28+
29+
// and load the index.html of the app.
30+
mainWindow.loadFile('index.html');
31+
block();
32+
};
33+
34+
// This method will be called when Electron has finished
35+
// initialization and is ready to create browser windows.
36+
// Some APIs can only be used after this event occurs.
37+
app.whenReady().then(() => {
38+
Sentry.profiler.startProfiler();
39+
createWindow();
40+
Sentry.profiler.stopProfiler();
41+
42+
app.on('activate', () => {
43+
// On macOS it's common to re-create a window in the app when the
44+
// dock icon is clicked and there are no other windows open.
45+
if (BrowserWindow.getAllWindows().length === 0) createWindow();
46+
});
47+
});
48+
49+
// Quit when all windows are closed, except on macOS. There, it's common
50+
// for applications and their menu bar to stay active until the user quits
51+
// explicitly with Cmd + Q.
52+
app.on('window-all-closed', () => {
53+
if (process.platform !== 'darwin') app.quit();
54+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<body>
4+
<h1>Hello From Profiled Electron App</h1>
5+
</body>
6+
</html>

dev-packages/e2e-tests/test-applications/node-profiling/package.json

+8-3
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,17 @@
77
"build": "node build.mjs && node build.shimmed.mjs",
88
"test": "node dist/index.js && node --experimental-require-module dist/index.js && node dist/index.shimmed.mjs",
99
"clean": "npx rimraf node_modules dist",
10-
"test:build": "npm run typecheck && npm run build",
11-
"test:assert": "npm run test"
10+
"test:electron": "$(pnpm bin)/electron-rebuild && playwright test",
11+
"test:build": "pnpm run typecheck && pnpm run build",
12+
"test:assert": "pnpm run test && pnpm run test:electron"
1213
},
1314
"dependencies": {
15+
"@electron/rebuild": "^3.7.0",
16+
"@playwright/test": "^1.48.2",
17+
"@sentry/electron": "latest || *",
1418
"@sentry/node": "latest || *",
15-
"@sentry/profiling-node": "latest || *"
19+
"@sentry/profiling-node": "latest || *",
20+
"electron": "^33.2.0"
1621
},
1722
"devDependencies": {},
1823
"volta": {
Binary file not shown.

packages/profiling-node/bindings/cpu_profiler.cc

+3-3
Original file line numberDiff line numberDiff line change
@@ -333,9 +333,8 @@ void SentryProfile::Start(Profiler *profiler) {
333333

334334
// Initialize the CPU Profiler
335335
profiler->cpu_profiler->StartProfiling(
336-
profile_title,
337-
{v8::CpuProfilingMode::kCallerLineNumbers,
338-
v8::CpuProfilingOptions::kNoSampleLimit, kSamplingInterval});
336+
profile_title, v8::CpuProfilingMode::kCallerLineNumbers, true,
337+
v8::CpuProfilingOptions::kNoSampleLimit);
339338

340339
// listen for memory sample ticks
341340
profiler->measurements_ticker.add_cpu_listener(id, cpu_sampler_cb);
@@ -1169,6 +1168,7 @@ napi_value Init(napi_env env, napi_value exports) {
11691168
}
11701169

11711170
Profiler *profiler = new Profiler(env, isolate);
1171+
profiler->cpu_profiler->SetSamplingInterval(kSamplingInterval);
11721172

11731173
if (napi_set_instance_data(env, profiler, FreeAddonData, NULL) != napi_ok) {
11741174
napi_throw_error(env, nullptr, "Failed to set instance data for profiler.");

0 commit comments

Comments
 (0)