Skip to content

Commit e09112d

Browse files
committed
fix: refactor fd cache system
1 parent b2084d9 commit e09112d

6 files changed

Lines changed: 215 additions & 206 deletions

File tree

ddprof-lib/src/main/cpp/nativeFdClassifier.cpp

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
#ifdef UNIT_TEST
1515
std::atomic<NativeFdClassifier::ProbeOverride> NativeFdClassifier::_probe_override{nullptr};
16+
std::atomic<uint64_t> NativeFdClassifier::_probe_count{0};
1617
#endif
1718

1819
NativeFdClassifier::NativeFdClassifier() {
@@ -28,9 +29,20 @@ NativeFdClassifier::NativeFdClassifier() {
2829
void NativeFdClassifier::setProbeOverrideForTest(ProbeOverride probe) {
2930
_probe_override.store(probe, std::memory_order_release);
3031
}
32+
33+
uint64_t NativeFdClassifier::probeCountForTest() {
34+
return _probe_count.load(std::memory_order_acquire);
35+
}
36+
37+
void NativeFdClassifier::resetProbeCountForTest() {
38+
_probe_count.store(0, std::memory_order_release);
39+
}
3140
#endif
3241

3342
uint8_t NativeFdClassifier::probeFdType(int fd) {
43+
#ifdef UNIT_TEST
44+
_probe_count.fetch_add(1, std::memory_order_relaxed);
45+
#endif
3446
int so_type;
3547
socklen_t solen = sizeof(so_type);
3648
int rc;
@@ -59,6 +71,19 @@ uint8_t NativeFdClassifier::probeFdType(int fd) {
5971
return errno == ENOTSOCK ? FD_TYPE_NON_SOCKET : 0;
6072
}
6173

74+
void NativeFdClassifier::cacheFdType(int fd, uint8_t type, uint32_t gen) {
75+
if (fd < 0 || type == 0) {
76+
return;
77+
}
78+
if (static_cast<size_t>(fd) < static_cast<size_t>(FD_TYPE_CACHE_SIZE)) {
79+
_fd_type_cache[fd].store((gen << FD_TYPE_GEN_SHIFT) | type,
80+
std::memory_order_release);
81+
} else {
82+
_high_fd_type_cache[highFdCacheIndex(fd)].store(highFdEntry(fd, gen, type),
83+
std::memory_order_release);
84+
}
85+
}
86+
6287
uint64_t NativeFdClassifier::highFdEntry(int fd, uint32_t gen, uint8_t type) {
6388
return (static_cast<uint64_t>(static_cast<uint32_t>(fd)) << 32)
6489
| (static_cast<uint64_t>(gen & FD_TYPE_GEN_MASK) << FD_TYPE_GEN_SHIFT)
@@ -95,7 +120,7 @@ uint8_t NativeFdClassifier::highFdType(int fd, uint32_t gen) {
95120
// the same fd number may later be reused for a socket.
96121
if (type != 0) {
97122
_high_fd_type_cache[index].store(highFdEntry(fd, gen, type),
98-
std::memory_order_release);
123+
std::memory_order_release);
99124
}
100125
return type;
101126
}
@@ -122,8 +147,7 @@ uint8_t NativeFdClassifier::fdType(int fd) {
122147
// probeFdType() returns 0 for transient errors such as EBADF. Do not cache those:
123148
// the same fd number may later be reused for a socket.
124149
if (type != 0) {
125-
_fd_type_cache[fd].store((gen << FD_TYPE_GEN_SHIFT) | type,
126-
std::memory_order_release);
150+
cacheFdType(fd, type, gen);
127151
}
128152
return type;
129153
}
@@ -136,6 +160,14 @@ bool NativeFdClassifier::isDatagramSocket(int fd) {
136160
return fdType(fd) == FD_TYPE_DATAGRAM_SOCKET;
137161
}
138162

163+
void NativeFdClassifier::cacheNonSocket(int fd) {
164+
if (fd < 0) {
165+
return;
166+
}
167+
uint32_t gen = _fd_cache_gen.load(std::memory_order_acquire);
168+
cacheFdType(fd, FD_TYPE_NON_SOCKET, gen);
169+
}
170+
139171
void NativeFdClassifier::clearHighFdType(int fd) {
140172
int index = highFdCacheIndex(fd);
141173
uint64_t cached = _high_fd_type_cache[index].load(std::memory_order_acquire);

ddprof-lib/src/main/cpp/nativeFdClassifier.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,15 @@ class NativeFdClassifier {
1717

1818
bool isStreamSocket(int fd);
1919
bool isDatagramSocket(int fd);
20+
void cacheNonSocket(int fd);
2021
void clearFdType(int fd);
2122
void clearFdTypeCache();
2223

2324
#ifdef UNIT_TEST
2425
using ProbeOverride = int (*)(int fd, int *so_type, int *probe_errno);
2526
static void setProbeOverrideForTest(ProbeOverride probe);
27+
static uint64_t probeCountForTest();
28+
static void resetProbeCountForTest();
2629
#endif
2730

2831
private:
@@ -45,12 +48,14 @@ class NativeFdClassifier {
4548
static bool highFdEntryMatches(uint64_t entry, int fd, uint32_t gen);
4649
static bool highFdEntryMatchesFd(uint64_t entry, int fd);
4750
static int highFdCacheIndex(int fd);
51+
void cacheFdType(int fd, uint8_t type, uint32_t gen);
4852
uint8_t highFdType(int fd, uint32_t gen);
4953
void clearHighFdType(int fd);
5054
uint8_t fdType(int fd);
5155

5256
#ifdef UNIT_TEST
5357
static std::atomic<ProbeOverride> _probe_override;
58+
static std::atomic<uint64_t> _probe_count;
5459
#endif
5560
};
5661

ddprof-lib/src/main/cpp/nativeSocketSampler.cpp

Lines changed: 10 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -49,23 +49,21 @@ std::atomic<bool> NativeSocketSampler::_active{false};
4949

5050
#ifdef UNIT_TEST
5151
static std::atomic<NativeSocketSampler::HookObserver> _native_socket_sampler_observer{nullptr};
52-
static std::atomic<uint64_t> _native_socket_sampler_socket_probe_count{0};
53-
std::atomic<NativeSocketSampler::ProbeOverride> NativeSocketSampler::_probe_override{nullptr};
5452

5553
void NativeSocketSampler::setHookObserverForTest(HookObserver observer) {
5654
_native_socket_sampler_observer.store(observer, std::memory_order_release);
5755
}
5856

5957
uint64_t NativeSocketSampler::socketProbeCountForTest() {
60-
return _native_socket_sampler_socket_probe_count.load(std::memory_order_acquire);
58+
return NativeFdClassifier::probeCountForTest();
6159
}
6260

6361
void NativeSocketSampler::resetSocketProbeCountForTest() {
64-
_native_socket_sampler_socket_probe_count.store(0, std::memory_order_release);
62+
NativeFdClassifier::resetProbeCountForTest();
6563
}
6664

6765
void NativeSocketSampler::setProbeOverrideForTest(ProbeOverride probe) {
68-
_probe_override.store(probe, std::memory_order_release);
66+
NativeFdClassifier::setProbeOverrideForTest(probe);
6967
}
7068

7169
void NativeSocketSampler::observeHookPhaseForTest(const char* phase, int fd, u8 op, ssize_t ret) {
@@ -111,135 +109,11 @@ std::string NativeSocketSampler::resolveAddr(int fd) {
111109
return std::string(buf);
112110
}
113111

114-
NativeSocketSampler::NativeSocketSampler() {
115-
for (int index = 0; index < FD_TYPE_CACHE_SIZE; index++) {
116-
_fd_type_cache[index].store(FD_TYPE_UNKNOWN, std::memory_order_relaxed);
117-
}
118-
for (int index = 0; index < HIGH_FD_TYPE_CACHE_SIZE; index++) {
119-
_high_fd_type_cache[index].store(0, std::memory_order_relaxed);
120-
}
121-
}
122-
123-
uint8_t NativeSocketSampler::probeFdType(int fd) {
124-
#ifdef UNIT_TEST
125-
_native_socket_sampler_socket_probe_count.fetch_add(1, std::memory_order_relaxed);
126-
#endif
127-
int so_type;
128-
socklen_t solen = sizeof(so_type);
129-
int rc;
130-
#ifdef UNIT_TEST
131-
ProbeOverride probe = _probe_override.load(std::memory_order_acquire);
132-
int probe_errno = 0;
133-
if (probe != nullptr) {
134-
rc = probe(fd, &so_type, &probe_errno);
135-
if (rc != 0) {
136-
errno = probe_errno;
137-
}
138-
} else
139-
#endif
140-
{
141-
rc = getsockopt(fd, SOL_SOCKET, SO_TYPE, &so_type, &solen);
142-
}
143-
if (rc == 0) {
144-
return so_type == SOCK_STREAM ? FD_TYPE_SOCKET : FD_TYPE_NON_SOCKET;
145-
}
146-
// Only stable negative verdicts are cached. Transient failures must leave
147-
// the fd unknown so a later reuse or successful probe can be observed.
148-
return errno == ENOTSOCK ? FD_TYPE_NON_SOCKET : FD_TYPE_UNKNOWN;
149-
}
150-
151-
uint64_t NativeSocketSampler::highFdEntry(int fd, uint8_t gen, uint8_t type) {
152-
return (static_cast<uint64_t>(static_cast<uint32_t>(fd)) << 32)
153-
| (static_cast<uint64_t>(gen & 0xF) << 4)
154-
| static_cast<uint64_t>(type);
155-
}
156-
157-
bool NativeSocketSampler::highFdEntryMatches(uint64_t entry, int fd, uint8_t gen) {
158-
return highFdEntryMatchesFd(entry, fd)
159-
&& (((entry >> 4) & 0xF) == (gen & 0xF));
160-
}
161-
162-
bool NativeSocketSampler::highFdEntryMatchesFd(uint64_t entry, int fd) {
163-
return static_cast<uint32_t>(entry >> 32) == static_cast<uint32_t>(fd);
164-
}
165-
166-
int NativeSocketSampler::highFdCacheIndex(int fd) {
167-
return static_cast<int>(static_cast<uint32_t>(fd)
168-
% static_cast<uint32_t>(HIGH_FD_TYPE_CACHE_SIZE));
169-
}
170-
171-
uint8_t NativeSocketSampler::highFdType(int fd, uint8_t gen) {
172-
int index = highFdCacheIndex(fd);
173-
uint64_t cached = _high_fd_type_cache[index].load(std::memory_order_acquire);
174-
if (highFdEntryMatches(cached, fd, gen)) {
175-
uint8_t type = static_cast<uint8_t>(cached & 0xF);
176-
if (type != FD_TYPE_UNKNOWN) {
177-
return type;
178-
}
179-
}
180-
181-
uint8_t type = probeFdType(fd);
182-
if (type != FD_TYPE_UNKNOWN) {
183-
_high_fd_type_cache[index].store(highFdEntry(fd, gen, type),
184-
std::memory_order_release);
185-
}
186-
return type;
187-
}
188-
189-
void NativeSocketSampler::cacheFdType(int fd, uint8_t gen, uint8_t type) {
190-
if (fd < 0 || type == FD_TYPE_UNKNOWN) {
191-
return;
192-
}
193-
if ((size_t)fd < (size_t)FD_TYPE_CACHE_SIZE) {
194-
_fd_type_cache[fd].store((uint8_t)(((gen & 0xF) << 4) | type),
195-
std::memory_order_release);
196-
} else {
197-
_high_fd_type_cache[highFdCacheIndex(fd)].store(highFdEntry(fd, gen, type),
198-
std::memory_order_release);
199-
}
200-
}
201-
202-
void NativeSocketSampler::clearHighFdType(int fd) {
203-
int index = highFdCacheIndex(fd);
204-
uint64_t cached = _high_fd_type_cache[index].load(std::memory_order_acquire);
205-
while (highFdEntryMatchesFd(cached, fd)) {
206-
if (_high_fd_type_cache[index].compare_exchange_weak(
207-
cached, 0, std::memory_order_acq_rel, std::memory_order_acquire)) {
208-
return;
209-
}
210-
}
211-
}
212-
213112
bool NativeSocketSampler::isSocket(int fd) {
214113
// Accepts any SOCK_STREAM socket (including AF_UNIX); AF_INET/AF_INET6 filtering
215114
// is deferred to resolveAddr() which is only called for sampled events. AF_UNIX
216115
// will produce an empty remoteAddress field in the JFR event.
217-
if (fd < 0) return false;
218-
// Acquire on the gen load pairs with the release on the gen-bump in start()
219-
// and on the cache cell store below; without it, on a weakly-ordered arch
220-
// (aarch64) a thread could observe a freshly written cell without the matching
221-
// gen bump (or vice versa), defeating the generation-tag invalidation contract.
222-
uint8_t gen = _fd_cache_gen.load(std::memory_order_acquire);
223-
if ((size_t)fd >= (size_t)FD_TYPE_CACHE_SIZE) {
224-
return highFdType(fd, gen) == FD_TYPE_SOCKET;
225-
}
226-
227-
uint8_t cached = _fd_type_cache[fd].load(std::memory_order_acquire);
228-
// High nibble encodes generation; entry is valid only when it matches current gen mod 16.
229-
if ((cached >> 4) == (gen & 0xF)) {
230-
uint8_t type = cached & 0xF;
231-
// A cached NON_SOCKET verdict is safe to trust: the worst case is that a
232-
// newly-socketed fd reuse under-samples until the next gen reset, which is
233-
// the documented accepted staleness tradeoff.
234-
if (type == FD_TYPE_NON_SOCKET) return false;
235-
// Cached SOCKET: trust the verdict on the hot path; revalidation is deferred
236-
// to recordEvent() on sampled write/read events (see revalidateSocket()).
237-
if (type == FD_TYPE_SOCKET) return true;
238-
}
239-
240-
uint8_t type = probeFdType(fd);
241-
cacheFdType(fd, gen, type);
242-
return type == FD_TYPE_SOCKET;
116+
return _fd_classifier.isStreamSocket(fd);
243117
}
244118

245119
void NativeSocketSampler::insertFdAddrLocked(int fd, std::string addr) {
@@ -258,11 +132,7 @@ void NativeSocketSampler::insertFdAddrLocked(int fd, std::string addr) {
258132
}
259133

260134
void NativeSocketSampler::clearFdCacheEntry(int fd) {
261-
if (fd >= 0 && (size_t)fd < (size_t)FD_TYPE_CACHE_SIZE) {
262-
_fd_type_cache[fd].store(FD_TYPE_UNKNOWN, std::memory_order_release);
263-
} else if (fd >= 0) {
264-
clearHighFdType(fd);
265-
}
135+
_fd_classifier.clearFdType(fd);
266136

267137
std::lock_guard<std::mutex> lock(_fd_cache_mutex);
268138
auto it = _fd_cache.find(fd);
@@ -278,10 +148,7 @@ bool NativeSocketSampler::revalidateSocket(int fd) {
278148
int rc = getsockopt(fd, SOL_SOCKET, SO_TYPE, &so_type, &solen);
279149
if (rc == 0 && so_type == SOCK_STREAM) return true;
280150
// fd was reused for a non-socket or is already closed; update the type cache.
281-
if (fd >= 0) {
282-
uint8_t gen = _fd_cache_gen.load(std::memory_order_acquire);
283-
cacheFdType(fd, gen, FD_TYPE_NON_SOCKET);
284-
}
151+
_fd_classifier.cacheNonSocket(fd);
285152
return false;
286153
}
287154

@@ -493,12 +360,9 @@ Error NativeSocketSampler::start(Arguments &args) {
493360
// (which carry no latency signal) are suppressed when the interval is large.
494361
_rate_limiter.start(init_interval, TARGET_EVENTS_PER_SECOND,
495362
PID_WINDOW_SECS, PID_P_GAIN, PID_I_GAIN, PID_D_GAIN, PID_CUTOFF_S);
496-
// Clear the fd->addr cache and reset the fd-type cache generation for the new
497-
// session so stale entries from a prior run cannot produce misattributed events
498-
// even if stop() was not called. clearFdCache() bumps _fd_cache_gen under the
499-
// mutex so the clear and the gen bump are atomic with respect to concurrent
500-
// isSocket() calls. A single call per start() keeps the mod-16 generation-wrap
501-
// budget at the full 16 cycles documented in nativeSocketSampler.h.
363+
// Clear the fd->addr cache and reset the fd-type classifier generation for
364+
// the new session so stale entries from a prior run cannot produce
365+
// misattributed events even if stop() was not called.
502366
clearFdCache();
503367
#ifdef DEBUG
504368
_send_hook_calls.store(0, std::memory_order_relaxed);
@@ -537,10 +401,7 @@ void NativeSocketSampler::clearFdCache() {
537401
std::lock_guard<std::mutex> lock(_fd_cache_mutex);
538402
_fd_cache.clear();
539403
_fd_lru_list.clear();
540-
// Bump the generation under the lock so the clear and the bump are atomic
541-
// with respect to concurrent isSocket() calls: no thread can insert an
542-
// entry tagged with the old generation after the map is cleared.
543-
_fd_cache_gen.fetch_add(1, std::memory_order_release);
404+
_fd_classifier.clearFdTypeCache();
544405
}
545406

546407
#else // !__linux__

0 commit comments

Comments
 (0)