Replace the blocking semaphore guard with a non-blocking bounded FIFO queue
+ semaphore pattern:
- No semaphore/queue when maxParallelRequestsCount=0 (default): direct doHttpCall,
identical to the old behavior.
- When a concurrency limit is set, incoming messages are enqueued via non-blocking
offer(); a full queue triggers onFailure immediately.
- tryProcess() acquires one semaphore slot and dispatches the next valid queued task.
Stale tasks (batch deadline expired) are dropped and the slot reused in the same pass.
- doFinally hook releases the semaphore and calls tryProcess() exactly once after any
terminal signal (success, error, cancel), preventing double-release and permit leaks.
- publishOn(externalCallExecutor) moves callbacks off reactor-netty I/O threads.
System-level safety caps are wired through thingsboard.yml → ActorSystemContext →
TbContext → TbHttpClient, scoped to rule-engine services only via @TbRuleEngineComponent:
actors.rule.external.http_client.max_parallel_requests (ACTORS_RULE_EXTERNAL_HTTP_CLIENT_MAX_PARALLEL_REQUESTS)
actors.rule.external.http_client.max_pending_requests (ACTORS_RULE_EXTERNAL_HTTP_CLIENT_MAX_PENDING_REQUESTS)
actors.rule.external.http_client.pool_max_connections (ACTORS_RULE_EXTERNAL_HTTP_CLIENT_POOL_MAX_CONNECTIONS)
Backward compat: TB_RE_HTTP_CLIENT_POOL_MAX_CONNECTIONS still honored via yaml fallback.
Observability: five AtomicLong counters (dispatched, success, failure, droppedQueueFull,
droppedStale) with periodic WARN anomaly logging including semaphorePermits for leak detection.
No configuration changes or upgrade scripts required — docker image update is sufficient.
Rename RestApiCallNodeSettings to TbHttpClientSettings
The settings are about HTTP client transport concerns (connection pool,
concurrency, queue depth), not REST API Call node business logic.
The new name matches the consumer (TbHttpClient) and the YAML path
(actors.rule.external.http_client.*).
Introduce TbCallback-based finish notification for submitted jobs,
allowing callers to be notified when a job reaches a terminal state
(COMPLETED, FAILED, CANCELLED) via cluster-wide ComponentLifecycleMsg
broadcast.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>