Skip to content
This repository was archived by the owner on May 1, 2023. It is now read-only.

Commit 8c7daaa

Browse files
Delyan Kratunovfacebook-github-bot
Delyan Kratunov
authored andcommitted
Use SignalHandler instead of forkjail
Summary: This diff revisits an old decision to use a forked process (via forkjail) to evaluate art compatibility. Reviewed By: aandreyeu Differential Revision: D32150889 fbshipit-source-id: cb71b28ccf450ba9a3bd7724948be5a6f2f440a6
1 parent 5d82420 commit 8c7daaa

File tree

2 files changed

+89
-58
lines changed

2 files changed

+89
-58
lines changed

cpp/profiler/ArtCompatibilityRunner.cpp

Lines changed: 87 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -24,23 +24,24 @@
2424
#include <unistd.h>
2525
#include <algorithm>
2626
#include <array>
27-
#include <chrono>
28-
#include <ios>
29-
#include <map>
27+
#include <mutex>
3028
#include <set>
31-
#include <sstream>
3229
#include <string>
3330
#include <system_error>
3431
#include <vector>
3532

3633
#include <time.h>
3734

38-
#include <forkjail/ForkJail.h>
35+
#include <profilo/profiler/SignalHandler.h>
36+
#include <profilo/util/common.h>
3937

4038
namespace fbjni = facebook::jni;
4139

4240
namespace facebook {
4341
namespace profilo {
42+
43+
using profiler::SignalHandler;
44+
4445
namespace artcompat {
4546

4647
struct JavaFrame {
@@ -177,6 +178,32 @@ uint64_t now() {
177178
return ((uint64_t)ts.tv_sec) * 1'000'000'000 + ts.tv_nsec;
178179
}
179180

181+
namespace {
182+
183+
struct SignalState {
184+
sigjmp_buf sigJmpBuf;
185+
std::atomic_bool inSection;
186+
uint64_t tid;
187+
};
188+
// Signal handler to safely bail out. This is inspired by how the
189+
// SamplingProfiler signal handling works, but is way simpler.
190+
191+
void JumpToSafetySignalHandler(
192+
SignalHandler::HandlerScope scope,
193+
int signum,
194+
siginfo_t* siginfo,
195+
void* ucontext) {
196+
SignalState& state = *(SignalState*)scope.GetData();
197+
198+
if (state.tid == threadID() && state.inSection.load()) {
199+
scope.siglongjmp(state.sigJmpBuf, 1);
200+
}
201+
202+
scope.CallPreviousHandler(signum, siginfo, ucontext);
203+
}
204+
205+
} // namespace
206+
180207
//
181208
// We collect two stack traces, from the same JNI function (and therefore VM
182209
// frame) - one from Java, using normal VM APIs and one using our
@@ -198,20 +225,20 @@ uint64_t now() {
198225
bool runJavaCompatibilityCheckInternal(
199226
versions::AndroidVersion version,
200227
profiler::JavaBaseTracer* tracer) {
228+
// Because we only have one instance of the signal handling state,
229+
// we wrap everything in a lock to serialize all callers and simplify the
230+
// logic.
231+
static std::mutex exclusiveRunLock;
232+
std::lock_guard<std::mutex> lg{exclusiveRunLock};
233+
201234
auto begin = now();
202-
constexpr int kExitCodeSuccess = 100;
203-
constexpr int kExitCodeFailure = 150;
204-
constexpr int kTimeoutSec = 1;
205235

206236
auto jlThread_class = getThreadClass();
207237
auto jlThread_currentThread = jlThread_class->getStaticMethod<jobject()>(
208238
"currentThread", "()Ljava/lang/Thread;");
209239
auto jlThread = jlThread_currentThread(jlThread_class);
210240

211-
//
212-
// We must collect the Java stack trace before we fork because of internal
213-
// allocation locks within art.
214-
//
241+
// Collect the Java stack trace
215242
auto beginJava = now();
216243
std::vector<JavaFrame> javaStack;
217244
try {
@@ -221,52 +248,49 @@ bool runJavaCompatibilityCheckInternal(
221248
}
222249
auto endJava = now();
223250

224-
// Performs initialization in the parent, before we fork.
225-
tracer->prepare();
226-
227-
auto beginCpp = now();
228-
forkjail::ForkJail jail(
229-
[&javaStack, tracer] {
230-
try {
231-
tracer->startTracing();
232-
233-
std::array<CppUnwinderJavaFrame, kStackSize> cppStack;
234-
auto cppStackSize = getCppStackTrace(tracer, cppStack);
235-
236-
if (compareStackTraces(cppStack, cppStackSize, javaStack)) {
237-
FBLOGV("compareStackTraces returned true");
238-
forkjail::ForkJail::real_exit(kExitCodeSuccess);
239-
}
240-
241-
FBLOGV("compareStackTraces returned false");
242-
} catch (...) {
243-
FBLOGV("Ignored exception");
244-
// intentionally ignored
245-
}
246-
247-
forkjail::ForkJail::real_exit(kExitCodeFailure);
248-
},
249-
kTimeoutSec);
250-
251+
// Collect our tracer's stack trace
251252
try {
252-
auto child = jail.forkAndRun();
253-
// Child process would never reach here. Only the parent continues.
254-
// Wait for the child to exit.
255-
int status = 0;
256-
257-
do {
258-
if (waitpid(child, &status, 0) != child) {
259-
throw std::system_error(errno, std::system_category(), "waitpid");
260-
}
261-
} while (!WIFEXITED(status) && !WIFSIGNALED(status));
253+
tracer->prepare();
254+
auto beginCpp = now();
255+
256+
tracer->startTracing();
257+
258+
bool cppSuccess = false;
259+
size_t cppStackSize = 0;
260+
std::array<CppUnwinderJavaFrame, kStackSize> cppStack;
261+
262+
// Sets up signal handlers for SIGSEGV and SIGBUS. Uses SignalHandler, so
263+
// cannot be run concurrently with SamplingProfiler's usage (but that's
264+
// okay, compatibility checks gate the SamplingProfiler usage).
265+
//
266+
// Ultimately, we do need to use the exact same safety mechanism as the
267+
// profiler to work around the exact same bugs in Android's signal handling.
268+
static SignalState state{};
269+
auto& handlerSegv =
270+
SignalHandler::Initialize(SIGSEGV, JumpToSafetySignalHandler);
271+
handlerSegv.SetData(&state);
272+
handlerSegv.Enable();
273+
274+
auto& handlerBus =
275+
SignalHandler::Initialize(SIGBUS, JumpToSafetySignalHandler);
276+
handlerBus.SetData(&state);
277+
handlerBus.Enable();
278+
279+
if (sigsetjmp(state.sigJmpBuf, 1) == 0) {
280+
state.tid = threadID();
281+
state.inSection.store(true);
282+
cppStackSize = getCppStackTrace(tracer, cppStack);
283+
state.inSection.store(false);
284+
285+
cppSuccess = true;
286+
} else {
287+
// Long jump from signal handler
288+
state.inSection.store(false);
289+
cppSuccess = false;
290+
}
262291

263-
FBLOGD(
264-
"Cpp stack child exited: %i status: %i (%s) signalled: %i signal: %i",
265-
WIFEXITED(status),
266-
WEXITSTATUS(status),
267-
WEXITSTATUS(status) == kExitCodeSuccess ? "success" : "failure",
268-
WIFSIGNALED(status),
269-
WTERMSIG(status));
292+
handlerSegv.Disable();
293+
handlerBus.Disable();
270294

271295
auto end = now();
272296
FBLOGD(
@@ -276,10 +300,16 @@ bool runJavaCompatibilityCheckInternal(
276300
(endJava - beginJava) / 1'000'000,
277301
(end - beginCpp) / 1'000'000);
278302

279-
if (!WIFEXITED(status) || WEXITSTATUS(status) != kExitCodeSuccess) {
303+
if (!cppSuccess) {
304+
FBLOGE("getCppStackTrace signalled");
280305
return false;
281306
}
282307

308+
if (!compareStackTraces(cppStack, cppStackSize, javaStack)) {
309+
FBLOGE("compareStackTraces returned false");
310+
return false;
311+
}
312+
FBLOGI("Compatibility check succeeded");
283313
return true;
284314
} catch (std::system_error& ex) {
285315
FBLOGE("Caught system error: %s", ex.what());

cpp/profiler/BUCK

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ fb_xplat_cxx_library(
9292
soname = "libprofilo_stacktrace_artcompat.so",
9393
deps = [
9494
":base_tracer",
95+
":signal_handler",
9596
":unwindc-tracer-5.0.0",
9697
":unwindc-tracer-5.1.0",
9798
":unwindc-tracer-6.0.0",
@@ -104,8 +105,8 @@ fb_xplat_cxx_library(
104105
":unwindc-tracer-9.0.0",
105106
profilo_path("deps/fb:fb"),
106107
profilo_path("deps/fbjni:fbjni"),
107-
profilo_path("deps/forkjail:forkjail"),
108108
profilo_path("cpp/logger/buffer:buffer"),
109+
profilo_path("cpp/util:util"),
109110
],
110111
)
111112

0 commit comments

Comments
 (0)