wu-sheng commented on PR #141:
URL: 
https://github.com/apache/skywalking-nodejs/pull/141#issuecomment-4898648384

   ## Design principle: this is a Node.js agent — be Node-native, don't port 
Java 1:1
   
   A general note for this PR and future `agent.core` work: **please don't 
mechanically translate the Java agent's structure into TypeScript.** The Java 
agent's shape is dictated by the JVM's threading model — background threads, 
`ReentrantLock`/`synchronized`, `DataCarrier` blocking queues, generation 
counters. Node.js is single-threaded with an event loop: there are no data 
races, so there are no locks to port, and the platform already gives us most of 
what those Java constructs exist to provide — `async`/`await`, `EventEmitter`, 
grpc-js's **event-driven** connectivity API, native stream backpressure, and 
OpenSSL for key parsing.
   
   Where the Java agent is the **contract** (env var names, meter names, 
wire/proto semantics, reconnect *policy*), match it. Where it's just 
**implementation shaped by the JVM**, use the Node primitive instead.
   
   ### Why copying Java hurts (the issues it caused in this PR)
   
   - **Locks/guards for a problem Node doesn't have.** Single-threaded ⇒ no 
concurrent mutation ⇒ a ported `synchronized`/lock becomes an async re-entrancy 
flag that only exists to prop up *other* ported machinery.
   - **Extra timers = extra event-loop wakeups**, and they can keep the host 
process alive. This is literally how the `watchConnectivityState` 24h deadline 
regressed into a ref'd timer holding the process open (M1) — v1 used `Infinity` 
and had no such timer.
   - **Reimplementing platform primitives ⇒ more code, more bugs.** Multiple 
defects in this PR trace directly to porting a Java idiom instead of using the 
Node one (see table).
   - **Dead abstractions ported ahead of need** become maintenance burden and 
imply capability that isn't actually wired up.
   
   ### Examples in this PR
   
   | Java-ism copied | Where | Issue | Node-native instead |
   |---|---|---|---|
   | `GRPCChannelManager.run()` background-thread **poll loop** + 
`checkInFlight` lock + `reconnect`/`reconnectCount` state machine | 
`GRPCChannelManager.ts` `boot()`/`runCheck()` | A 30s `setInterval` + lock is 
armed for **every** agent, even the default single-backend case — 
reimplementing connectivity detection that grpc-js already does event-driven. 
v1 had none of this. | v1 was correct: event-driven 
`channel.watchConnectivityState(state, Infinity, cb)` that re-arms itself; 
grpc-js auto-reconnects a channel with backoff on its own. Arm a timer only 
when it has real work — `isResolveDnsPeriodically` or `grpcServers.length > 1`. 
|
   | `watcherGeneration` monotonic counter to reject stale callbacks | 
`GRPCChannelManager.ts:70` | A JVM generation-guard pattern duplicating a check 
that already exists. | The closure's `this.managedChannel !== managed` 
reference check (present since v1) is sufficient single-threaded. Delete the 
counter. |
   | Full **command framework**: `CommandService` queue + `drainScheduled` + 
`setImmediate` drain + `CommandExecutorService` + `CommandSerialNumberCache` | 
`commands/*` | `registerExecutor` is **never called**, so the executor map is 
always empty and every command just flows through the queue → drain → 
`warn('unsupported command')`. The entire pipeline is currently pure overhead. 
| Node has no worker threads to hand off to — dispatch directly from 
`receiveCommand`. Reintroduce structure only when real executors exist. |
   | Serial-number dedup as `string[]` + O(n) `Array.includes` (Java bounded 
queue) | `CommandSerialNumberCache.ts:38` | Linear scan + manual `shift()` 
eviction to mimic an `ArrayBlockingQueue`. | `Set` — O(1) membership, 
insertion-ordered so oldest-eviction is trivial. |
   | PKCS#1→PKCS#8 ASN.1 hand-rewrap (Java `KeyFactory` requires PKCS#8) | 
`PrivateKeyUtil` — **already removed in `ade5c3b`** ✅ | Java's constraint 
doesn't exist in Node; the hand-built DER length prefix was also a latent 
corruption risk. | Node/OpenSSL (`tls.createSecureContext`) accept PKCS#1 PEM 
directly — this is the canonical example, and removing it was exactly right. |
   
   The `PrivateKeyUtil` cleanup in the latest commit is the right instinct — 
let's apply the same lens to the connectivity poll loop and the command 
pipeline. Net: prefer grpc-js/EventEmitter/async primitives over ported 
threads, locks, and queues; keep Java only where it defines the *behavior*, not 
the *mechanism*.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to