Hacktoberfest 2026:维护者为十月标记出来的 issue,仍然开放、适合新手。 浏览 Hacktoberfest issue

[Bug]: TagBatch reuses a cached PlcReadRequest bound to a closed connection, so fetches fail after the first and never recover after a PLC restart

未关闭
#2,832 0 条评论 0 个 reaction 已指派 1 人 在 GitHub 查看

维护者通常 1 天内回复

@chrisdutz 已经在做这个了。

开始于 2026年10月26日。

评估

难度
3/5
预计耗时
1-2 天
新手友好度
72/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
技术栈
java

调研方向

Read plc4j/tools/event-pump/src/main/java/org/apache/plc4x/java/tools/eventpump/TagBatch.java, focusing on fetchTags(), request caching, connection closing, and invalidateCache(). Reproduce with the issue's TagBatch example using DefaultPlcDriverManager, then repeat with PlcConnectionCache and restart the Modbus simulator. Done means later fetches use the current connection and continue after a PLC restart; add or update event-pump tests covering repeated fetches and recovery.

由索引模型根据 Issue 内容生成。

描述

What happened?

TagBatch in plc4j-tools-event-pump builds its PlcReadRequest once and caches it (keyed only on the tag snapshot), but it obtains a connection from the configured PlcConnectionFactory on every fetch and closes that connection after the fetch. A PlcReadRequest is bound to the connection whose builder created it (DefaultPlcReadRequest keeps the PlcReader; execute() calls reader.read(this)), so the cached request keeps targeting the connection of the first fetch. Once that connection is closed, every later fetch fails without ever using the connection that was just opened for it.

Observed against a Modbus TCP simulator (oitc/modbus-server, Docker) with plc4j-tools-event-pump 1.0.0, plc4j-driver-modbus 1.0.0, TimerTrigger(100 ms), three tags (coil:1, holding-register:1:INT, holding-register:2:INT):

  1. DefaultPlcDriverManager as connection factory (a new connection per fetch): fetch 1 succeeds, fetch 2 and every later attempt fail immediately with PlcRuntimeException: Failed to send request, root cause TransportException: Transport is closed at TcpTransportInstance.ensureOpen(TcpTransportInstance.java:319). Each attempt still logs "Modbus TCP connection established" for a connection that is never used. In 5 s: 1 fetch, 3 errors, back-off 1 s, 2 s, 4 s.
  2. PlcConnectionCache as connection factory (one physical connection, a lease per fetch): 49 fetches in 5 s while the simulator is up. After docker stop / docker start of the simulator the batch never recovers: 0 fetches in the following 65 s, every attempt fails with Transport is closed, and no "Connection refused" ever appears because the network is never touched. Even after the cache's idle validation detected the dead connection and opened a new one (at about +58 s), the next fetch failed again: the cached request still targets the old connection.

Cause, same on develop as in 1.0.0 (plc4j/tools/event-pump/src/main/java/org/apache/plc4x/java/tools/eventpump/TagBatch.java):

  • fetchTags() calls connectionFactory.getConnection(connectionString) per fetch (line 516) and closes the connection in the completion handler (lines 604 and 621).
  • The read request is cached in cachedReadRequest / cachedTagSnapshot (lines 122-123) and reused whenever the tag snapshot is unchanged (lines 529-541).
  • invalidateCache() (lines 712-715) is private and is only called by addTag(s) / removeTag(s) / clearTags, never on a fetch error or on a different connection.

Expected: a fetch executes its read request on the connection it obtained for that fetch, so a batch over DefaultPlcDriverManager keeps working and a batch over PlcConnectionCache recovers once the PLC is reachable again (the back-off then does its job instead of retrying a dead request forever).

Possible fixes, in order of simplicity: build the request on the connection of the current fetch (the PlcReadRequest.Builder cost is small compared to a network round trip); or cache the request per connection instance; or at least call invalidateCache() on every fetch error.

Workaround we verified: calling removeTag(name) + addTag(name, address) from TagBatchListener.onError forces a rebuild; with PlcConnectionCache the batch then recovers after a simulator restart (275 fetches in the 30 s after docker start, 3 errors in total). Side effect: the tag order in later responses changes.

Reproduction (JDK 21, plc4j-api, plc4j-tools-event-pump, plc4j-driver-modbus 1.0.0 on the classpath; docker run -d -p 127.0.0.1:5020:5020 oitc/modbus-server:latest):

TagBatch batch = TagBatch.builder()
        .withBatchId("repro")
        .withConnectionFactory(new DefaultPlcDriverManager())
        .withConnectionString("modbus-tcp://127.0.0.1:5020")
        .addTagAddress("c1", "coil:1")
        .withTrigger(new TimerTrigger(100, TimeUnit.MILLISECONDS))
        .withListener(new TagBatchListener() {
            @Override
            public void onTagsFetched(TagBatch b, PlcReadResponse r) {
                System.out.println("fetched " + r.getResponseCode("c1"));
            }

            @Override
            public void onError(TagBatch b, Throwable t) {
                System.out.println("error " + t);
            }
        })
        .build();
batch.start();
Thread.sleep(5000);
batch.close();

Output: one fetched OK, then only error ... Transport is closed lines. Replace the factory with PlcConnectionCache.getBuilder().withConnectionFactory(new DefaultPlcDriverManager()).build(), restart the simulator while the batch runs, and the error lines never stop.

Found while porting Apache Camel's camel-plc4x triggered consumer from plc4j-scraper to the event pump (CAMEL-25617).

Version

v1.0.0 (code unchanged on develop as of 2026-10-23)

Programming Languages
  • plc4c
  • plc4go
  • plc4j
  • plc4net
  • plc4py
Protocols
  • Modbus (used for the reproduction; the cause is protocol-independent)
主要语言
Java
星标
1.8k
派生
512
平均合并
2 天 2 小时
30 天内合并 PR
34

环境准备

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

apache/plc4x 的其他 Issue

查看 apache/plc4x 的全部 Issue

相似的 Issue

更多 Java Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。