Skip to content

Commit 8bf4929

Browse files
committed
ignore threads that have SIGPROF blocked
1 parent 68286c6 commit 8bf4929

File tree

2 files changed

+102
-0
lines changed

2 files changed

+102
-0
lines changed

Sources/CProfileRecorderSampler/os_dep_linux.c

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,54 @@
2929
#include "interface.h"
3030
#include "common.h"
3131

32+
// Check if a specific signal is blocked for a given thread.
33+
// Returns: 1 if signal is blocked, 0 if not blocked, -1 on error.
34+
static int swipr_is_signal_blocked(swipr_os_dep_thread_id tid, int signum) {
35+
if (signum < 1 || signum > 64) {
36+
return -1;
37+
}
38+
39+
char path[64];
40+
snprintf(path, sizeof(path), "/proc/self/task/%d/status", tid);
41+
42+
int fd = open(path, O_RDONLY);
43+
if (fd < 0) {
44+
return -1;
45+
}
46+
47+
char buffer[4096];
48+
ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
49+
int close_result = close(fd);
50+
if (bytes_read <= 0 || (close_result != 0 && !(close_result == -1 && errno == EINTR))) {
51+
return -1;
52+
}
53+
buffer[bytes_read] = '\0';
54+
55+
// Find the SigBlk line
56+
const char *sigblk_line = strstr(buffer, "\nSigBlk:");
57+
if (!sigblk_line) {
58+
sigblk_line = strstr(buffer, "SigBlk:");
59+
if (!sigblk_line) {
60+
return -1;
61+
}
62+
} else {
63+
sigblk_line++; // Skip the newline
64+
}
65+
66+
// Skip "SigBlk:" and whitespace
67+
sigblk_line += 7;
68+
while (*sigblk_line == ' ' || *sigblk_line == '\t') {
69+
sigblk_line++;
70+
}
71+
72+
// Parse the hexadecimal mask
73+
unsigned long long mask = strtoull(sigblk_line, NULL, 16);
74+
75+
// Check if the signal bit is set (signals are 1-indexed)
76+
int bit_position = signum - 1;
77+
return (mask & (1ULL << bit_position)) ? 1 : 0;
78+
}
79+
3280
struct thread_info *swipr_os_dep_create_thread_list(size_t *all_threads_count) {
3381
struct thread_info *all_threads = calloc(sizeof(struct thread_info), SWIPR_MAX_MUTATOR_THREADS);
3482
if (!all_threads) {
@@ -61,6 +109,13 @@ struct thread_info *swipr_os_dep_create_thread_list(size_t *all_threads_count) {
61109

62110
pid_t tid = atol(ent->d_name);
63111
if (tid != 0 && tid != my_tid) {
112+
// Check if SIGPROF is blocked for this thread
113+
int sigprof_blocked = swipr_is_signal_blocked(tid, SIGPROF);
114+
if (sigprof_blocked > 0) {
115+
// Skip threads that have SIGPROF blocked
116+
continue;
117+
}
118+
64119
int idx = next_index++;
65120
all_threads[idx].ti_id = tid;
66121
char file_path[128] = {0};

Tests/ProfileRecorderTests/ProfileRecorderTests.swift

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,53 @@ final class ProfileRecorderTests: XCTestCase {
8484
)
8585
}
8686

87+
func testSamplingWithThreadThatBlocksSIGPROF() async throws {
88+
guard ProfileRecorderSampler.isSupportedPlatform else {
89+
return
90+
}
91+
92+
let threadReady = DispatchSemaphore(value: 0)
93+
let threadUnblock = DispatchSemaphore(value: 0)
94+
let threadDone = DispatchSemaphore(value: 0)
95+
96+
let blockingThread = Thread {
97+
var set = sigset_t()
98+
sigemptyset(&set)
99+
sigaddset(&set, SIGPROF)
100+
pthread_sigmask(SIG_BLOCK, &set, nil)
101+
102+
threadReady.signal()
103+
threadUnblock.wait()
104+
threadDone.signal()
105+
}
106+
blockingThread.start()
107+
108+
threadReady.wait()
109+
110+
let startTime = DispatchTime.now()
111+
XCTAssertNoThrow(
112+
try ProfileRecorderSampler.sharedInstance.requestSamples(
113+
outputFilePath: "\(self.tempDirectory!)/samples.samples",
114+
count: 10,
115+
timeBetweenSamples: .nanoseconds(0),
116+
eventLoop: self.group.next()
117+
).wait()
118+
)
119+
let elapsed = Double(DispatchTime.now().uptimeNanoseconds - startTime.uptimeNanoseconds) / 1_000_000_000.0
120+
XCTAssertLessThan(elapsed, 1.0, "Sampling took \(elapsed) seconds, expected < 1 second")
121+
122+
threadUnblock.signal()
123+
threadDone.wait()
124+
125+
let sampleData = try await ByteBuffer(
126+
contentsOf: FilePath("\(self.tempDirectory!)/samples.samples"),
127+
maximumSizeAllowed: .mebibytes(32)
128+
)
129+
XCTAssertGreaterThan(sampleData.readableBytes, 0, "Sample file should not be empty")
130+
let sampleString = String(buffer: sampleData)
131+
XCTAssertTrue(sampleString.contains("[SWIPR] SMPL"), "Sample file should contain sample data")
132+
}
133+
87134
func testSamplingWhilstThreadsAreCreatedAndDying() throws {
88135
guard ProfileRecorderSampler.isSupportedPlatform else {
89136
return

0 commit comments

Comments
 (0)