@@ -49,23 +49,21 @@ std::atomic<bool> NativeSocketSampler::_active{false};
4949
5050#ifdef UNIT_TEST
5151static 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
5553void NativeSocketSampler::setHookObserverForTest (HookObserver observer) {
5654 _native_socket_sampler_observer.store (observer, std::memory_order_release);
5755}
5856
5957uint64_t NativeSocketSampler::socketProbeCountForTest () {
60- return _native_socket_sampler_socket_probe_count. load (std::memory_order_acquire );
58+ return NativeFdClassifier::probeCountForTest ( );
6159}
6260
6361void NativeSocketSampler::resetSocketProbeCountForTest () {
64- _native_socket_sampler_socket_probe_count. store ( 0 , std::memory_order_release );
62+ NativeFdClassifier::resetProbeCountForTest ( );
6563}
6664
6765void NativeSocketSampler::setProbeOverrideForTest (ProbeOverride probe) {
68- _probe_override. store (probe, std::memory_order_release );
66+ NativeFdClassifier::setProbeOverrideForTest (probe );
6967}
7068
7169void 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-
213112bool 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
245119void NativeSocketSampler::insertFdAddrLocked (int fd, std::string addr) {
@@ -258,11 +132,7 @@ void NativeSocketSampler::insertFdAddrLocked(int fd, std::string addr) {
258132}
259133
260134void 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