vineetsaurabh commented on PR #29489:
URL: https://github.com/apache/camel/pull/29489#issuecomment-6773959747

   
   > 1. **A null client can get stuck in the pool.** `createClient` returns 
`null` on interruption (`DeviceClientPool.java:305-309`), and `acquire` caches 
it (`:186-187`). Every later send for that device then fails with an NPE. Each 
failed send also touches the entry, so the idle evictor never removes it. Could 
`createClient` throw instead (re-interrupt the thread first)? Also, if 
`subscribeToDesiredProperties` (`:300`) fails after `open(true)`, the opened 
client is never closed.
   
   Fixed: client creation now fails fast on interruption instead of caching a 
null client, and any client opened before a failed 
subscribeToDesiredProperties() setup is closed immediately to avoid leaking 
half-open connections.
   
   > 2. **Two connections for one device** (gnodet-bot's thread at `:187`). Two 
first sends for the same device both open a client before `putIfAbsent`, and 
IoT Hub drops one of the two connections. A per-key holder avoids this, e.g. 
`ConcurrentHashMap<String, CompletableFuture<PooledClient>>` with 
`computeIfAbsent`, opening the client outside the map.
   
   Fixed: client creation is now serialized per device key before 
createClient() is invoked, preventing two threads from concurrently opening two 
Azure IoT Hub connections for the same device and eliminating the 
duplicate-connect race
   
   > 3. **Telemetry body conversion** (`TelemetryDeliveryStrategy.java:60-70`). 
Only `byte[]`/`String` are accepted. Stream caching is on by default, so an 
HTTP or file body arrives as `InputStreamCache` and fails. 
`exchange.getIn().getMandatoryBody(byte[].class)` in the producer would cover 
all the type converters.
   > 
   
   Fixed: telemetry now resolves through 
exchange.getIn().getMandatoryBody(byte[].class) before dispatch, so Camel’s 
converter chain handles InputStream/InputStreamCache payloads as expected. The 
delivery strategy remains strict and only accepts the resolved byte[] payload.
   
   > **Important** 
   > 4. **Eviction vs. acquire.** Between `pool.get` (`:178`) and 
`existing.acquire()` (`:181`), the evictor can still see `inUse == 0` with an 
old `lastUsed`, then remove and close the client (`:333-335`). Doing the 
acquire inside `pool.computeIfPresent(...)`, and the eviction as 
`computeIfPresent(key, (k, v) -> v.inUse.get() == 0 && idle ? null : v)` with 
the close afterwards, makes both atomic. Neither lambda does any I/O. 
   
   Fixed: the idle-eviction check and the in-use transition are now performed 
atomically per device key using computeIfPresent, preventing an evictor from 
closing a client while another thread is acquiring it. The actual close still 
happens after the map update, outside the lambda.
   
   > 5. **Broken clients are never replaced.** The Javadoc (`:40-41`) says send 
operations remove broken clients, but they no longer do. The SDK does expose 
connection state: `setConnectionStatusChangeCallback`. Once the SDK stops 
reconnecting, every send fails until the client has been idle for 5 minutes, 
which never happens under steady traffic. What do you think about evicting on a 
terminal `DISCONNECTED` status? 
   
   I fixed this by evicting the client on the SDK’s terminal DISCONNECTED 
callback, not only on idle timeout. This prevents stale clients from being 
reused indefinitely under steady traffic. Added a regression test for the 
callback path
   
   > 6. **Ignored endpoint options.** The pool is built from the component 
configuration only. So `deviceClientIdleTimeout` and 
`deviceClientEvictionInterval` on an endpoint URI 
(`IoTHubConfiguration.java:60-66`) have no effect. Could they become component 
options? 
   
   These are pool-level settings rather than endpoint-level settings. The 
component builds one shared DeviceClientPool from the component configuration, 
so URI values for deviceClientIdleTimeout and deviceClientEvictionInterval are 
ignored. Keeping them as component options matches the actual behavior.
   
   >7. **Tests.**
   > * The module still uses `camel-test-junit5` (`pom.xml:51`, 
`IoTHubComponentTest`); all sibling Azure modules use `camel-test-junit6`.
   > * `DeviceClientPoolTest` only covers `getOrCreate`, which production does 
not call. A test of the `Lease` path would help: in-use skip, release, and a 
concurrent first acquire.
   > * A test of `IoTHubProducer.process` itself is still missing.
   > 
   
   I fixed the test gaps by:
   * switching the module from camel-test-junit5 to camel-test-junit6
   * adding lease-path coverage for:
       *  in-use reuse / release behavior
       * concurrent first acquire creating a single client
   * adding a producer-level regression for IoTHubProducer.process(...) with a 
stream-backed telemetry body to verify conversion and send semantics
   
   > **Minor**
   > 
   > * `touchAndMaybeEvict` (`:314-319`) touches the client and then compares 
`now - now`, so the eviction branch never runs. Only touching on release is 
enough, because the evictor already handles idle clients.
   
   Already covered
   
   > * In `BOTH` mode without the twin header, telemetry is sent first and then 
`TwinDeliveryStrategy` throws, so a redelivery sends the telemetry twice. Could 
the producer validate this before sending?
   
   I fixed it by validating the required twin header before dispatching 
telemetry, so invalid BOTH requests fail fast and do not send telemetry before 
the twin validation error.
   
   > * In `clean()`, an empty nested map becomes `null` 
(`TwinDeliveryStrategy.java:102-104`, `:112-113`), which deletes the whole 
nested property in IoT Hub.
   
   An empty nested map was being converted to null, and IoT Hub interprets that 
as deleting the whole nested property. I fixed it so empty nested maps are 
preserved as empty objects rather than being turned into null, and added a 
regression test covering that case.”
   
   > * The SDK has timeout overloads for twin calls: `getTwin(int)`, 
`updateReportedProperties(TwinCollection, int)`. So `sendTimeout` could cover 
twin updates too, and its description (`IoTHubConfiguration.java:52`) is not 
accurate.
   
   I updated the implementation to use sendTimeout for both telemetry sends and 
twin reported-property updates, and corrected the configuration wording 
accordingly
   
   > * A new component does not need the deprecated `sendTimeoutMillis` alias 
(`IoTHubConfiguration.java:55-58`).
   
   I removed it and kept only the sendTimeout option.
   
   > * Unused public API could go: `getOrCreate`, `lease`/`acquire`, 
`getClient`/`client`, `remove(...)`. The no-op `IoTHubProducer.doStop` can go 
too.
   
   Trimmed the API back to the minimal required surface so the component 
exposes only the behavior it actually uses.
   
   


-- 
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