Skip to content

CAMEL-24045: camel-sql - Fix connection leak with outputType=StreamList when the statement is not a query#24656

Merged
Croway merged 2 commits into
apache:mainfrom
Croway:CAMEL-24045-sql-streamlist-connection-leak
Jul 13, 2026
Merged

CAMEL-24045: camel-sql - Fix connection leak with outputType=StreamList when the statement is not a query#24656
Croway merged 2 commits into
apache:mainfrom
Croway:CAMEL-24045-sql-streamlist-connection-leak

Conversation

@Croway

@Croway Croway commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

JIRA: https://issues.apache.org/jira/browse/CAMEL-24045

SqlProducer.processStreamList() takes a raw connection from the DataSource so the result set can stay open while the exchange is routed. When PreparedStatement.execute() returns false (INSERT/UPDATE/DELETE/DDL), the result-set branch was skipped: no ResultSetIteratorCompletion was registered and neither the connection nor the statement was ever closed — the catch block only cleaned up on exception. Since the executed SQL can vary per exchange via the CamelSqlQuery header or useMessageBodyForSql=true, each such exchange leaked one pooled connection, eventually exhausting the pool.

Changes:

  • Close the statement and return the connection to the pool when the executed statement produced no result set
  • Set the CamelSqlUpdateCount header from getUpdateCount() for the non-query case, consistent with the non-query path in processInternal() (review follow-up)
  • New test with a connection-counting DataSource proving no connection stays open after the exchange completes and asserting the update-count header (fails on current main, passes with the fix); existing StreamList producer/consumer tests unaffected

Claude Code on behalf of Croway

🤖 Generated with Claude Code

@Croway Croway requested review from davsclaus and oscerd July 13, 2026 09:44
@github-actions

Copy link
Copy Markdown
Contributor

🌟 Thank you for your contribution to the Apache Camel project! 🌟
🤖 CI automation will test this PR automatically.

🐫 Apache Camel Committers, please review the following items:

  • First-time contributors require MANUAL approval for the GitHub Actions to run
  • You can use the command /component-test (camel-)component-name1 (camel-)component-name2.. to request a test from the test bot although they are normally detected and executed by CI.
  • You can label PRs using skip-tests and test-dependents to fine-tune the checks executed by this PR.
  • Build and test logs are available in the summary page. Only Apache Camel committers have access to the summary.

⚠️ Be careful when sharing logs. Review their contents before sharing them publicly.

@gnodet gnodet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean, minimal fix for a genuine connection leak in SqlProducer.processStreamList() when the executed statement is not a query (INSERT/UPDATE/DELETE/DDL). The fix correctly closes the statement and connection in the previously-missing else branch.

Scanner coverage: No static analysis tools (PMD, Checkstyle, semgrep, SpotBugs) available. Rely on CI.

Notes:

  • The fix correctly relies on Spring's JdbcUtils.closeStatement() and JdbcUtils.closeConnection(), both of which handle nulls and swallow exceptions internally, so sequential close calls are safe.
  • The test is well-crafted: the ConnectionCountingDataSource using JDK Proxy tracks connection lifecycle and verifies both the functional outcome and the resource invariant (zero open connections).

One minor observation: the new else branch does not set the SQL_UPDATE_COUNT header, unlike the equivalent non-query path in processInternal(). For completeness, consider adding exchange.getIn().setHeader(SqlConstants.SQL_UPDATE_COUNT, ps.getUpdateCount()) before closing the statement. This is low severity since StreamList mode with non-queries is an edge case.

This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.

Claude Code review on behalf of @gnodet

@Croway

Croway commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @gnodet I've added the header

@Croway Croway requested a review from gnodet July 13, 2026 09:58

@oscerd oscerd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed with focus on resource lifecycle across all execution paths — the fix is correct and I have no change requests. Summary of the verification:

The bug is real and the fix closes exactly the leaked path. On current main, processStreamList (SqlProducer.java:260-269) only handles ps.execute() == true: the ResultSet path hands con/ps/rs ownership to ResultSetIterator and defers cleanup to ResultSetIteratorCompletion at exchange completion. When the statement is not a query (execute() returns false), the method returns a null iterator and con/ps are never closed — the catch block (SqlProducer.java:272-274) only covers the exception path. The new else { closeStatement(ps); closeConnection(con); } branch closes them exactly once, cannot interfere with the streaming path (it only runs when there is no ResultSet to stream), and cannot double-close on exception (Spring's JdbcUtils.close* never throw, so the branch can't re-enter the catch).

No double-close on the streaming side either: ResultSetIterator.close() is idempotent via AtomicBoolean.compareAndSet, which covers the self-close-on-exhaustion and empty-result-set cases.

Test quality is a highlight: the regression test asserts the actual invariant (openConnections() == 0 via a counting DelegatingDataSource proxy) rather than merely exercising the path, is fully synchronous (no Thread.sleep, native assertIsSatisfied), and fails on unfixed main by inspection.

History check: the missing else traces back to the original CAMEL-9849 StreamList implementation — there is no prior intentional decision to keep connections open on non-query statements, so this does not revert deliberate behavior. Behavior parity is preserved (body/headers untouched on the non-query path, as before; the absent CamelSqlUpdateCount on this path is pre-existing and would be a separate enhancement).

Two merge-time notes:

  1. CI: the two build jobs (JDK 17/25) were still pending at review time — worth confirming green before merge.
  2. The PR body declares "Generated with Claude Code" but the commit has no AI co-authorship trailer — per the repo's AI guidelines the squash-merge commit message should include the Co-authored-by trailer for the AI assistant.

Reviewed with Claude Code (Fable 5) on behalf of oscerd. This review was generated by an AI agent and may contain inaccuracies; please verify all suggestions before applying.

@gnodet gnodet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code review on behalf of @gnodet

Re-review focused on the new commit (2864ca45): CAMEL-24045: camel-sql - Set CamelSqlUpdateCount header for non-query StreamList statements

This commit adds exactly two lines and directly implements the suggestion from the prior review:

  1. SqlProducer.java: Sets CamelSqlUpdateCount via ps.getUpdateCount() in the non-query else branch of processStreamList(), before closeStatement(ps) — the ordering is correct since the update count is not accessible after the statement is closed.

  2. Test: Adds mock.expectedHeaderReceived(SqlConstants.SQL_UPDATE_COUNT, 1) asserting the header value matches the expected single-row update.

The pattern is consistent with the equivalent non-query path in processInternal() (line 218). No issues found.

Scanner coverage: No standalone static analysis tools (PMD, Checkstyle, semgrep, SpotBugs) available. Relying on CI for validation.

This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.

@gnodet gnodet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code review on behalf of @gnodet

Re-review focused on the new commit (2864ca45): CAMEL-24045: camel-sql - Set CamelSqlUpdateCount header for non-query StreamList statements

This commit adds exactly two lines and directly implements the suggestion from the prior review:

  1. SqlProducer.java: Sets CamelSqlUpdateCount via ps.getUpdateCount() in the non-query else branch of processStreamList(), before closeStatement(ps) — the ordering is correct since the update count is not accessible after the statement is closed.

  2. Test: Adds mock.expectedHeaderReceived(SqlConstants.SQL_UPDATE_COUNT, 1) asserting the header value matches the expected single-row update.

The pattern is consistent with the equivalent non-query path in processInternal() (line 218). No issues found.

Scanner coverage: No standalone static analysis tools (PMD, Checkstyle, semgrep, SpotBugs) available. Relying on CI for validation.

This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 CI tested the following changed modules:

  • components/camel-sql

🔬 Scalpel shadow comparison — Scalpel: 11 tested, 29 compile-only — current: 11 all tested

Maveniverse Scalpel detected 40 affected modules (current approach: 11).

⚠️ Modules only in Scalpel (29)
  • apache-camel
  • camel-allcomponents
  • camel-catalog
  • camel-catalog-console
  • camel-catalog-lucene
  • camel-catalog-maven
  • camel-catalog-suggest
  • camel-componentdsl
  • camel-csimple-maven-plugin
  • camel-endpointdsl
  • camel-endpointdsl-support
  • camel-itest
  • camel-jbang-core
  • camel-jbang-it
  • camel-jbang-main
  • camel-jbang-plugin-edit
  • camel-jbang-plugin-generate
  • camel-jbang-plugin-kubernetes
  • camel-jbang-plugin-test
  • camel-kamelet-main
  • camel-launcher
  • camel-report-maven-plugin
  • camel-route-parser
  • camel-yaml-dsl
  • camel-yaml-dsl-deserializers
  • camel-yaml-dsl-maven-plugin
  • coverage
  • docs
  • dummy-component

Skip-tests mode would test 11 modules (1 direct + 10 downstream), skip tests for 29 (generated code, meta-modules)

Modules Scalpel would test (11)
  • camel-jbang-mcp
  • camel-jbang-plugin-mcp
  • camel-jbang-plugin-route-parser
  • camel-jbang-plugin-tui
  • camel-jbang-plugin-validate
  • camel-jta
  • camel-kafka
  • camel-launcher-container
  • camel-sql
  • camel-yaml-dsl-validator
  • camel-yaml-dsl-validator-maven-plugin
Modules with tests skipped (29)
  • apache-camel
  • camel-allcomponents
  • camel-catalog
  • camel-catalog-console
  • camel-catalog-lucene
  • camel-catalog-maven
  • camel-catalog-suggest
  • camel-componentdsl
  • camel-csimple-maven-plugin
  • camel-endpointdsl
  • camel-endpointdsl-support
  • camel-itest
  • camel-jbang-core
  • camel-jbang-it
  • camel-jbang-main
  • camel-jbang-plugin-edit
  • camel-jbang-plugin-generate
  • camel-jbang-plugin-kubernetes
  • camel-jbang-plugin-test
  • camel-kamelet-main
  • camel-launcher
  • camel-report-maven-plugin
  • camel-route-parser
  • camel-yaml-dsl
  • camel-yaml-dsl-deserializers
  • camel-yaml-dsl-maven-plugin
  • coverage
  • docs
  • dummy-component

ℹ️ Shadow mode — Scalpel observes but does not affect test execution. Learn more

All tested modules (40 modules)
  • Camel :: All Components Sync point
  • Camel :: Assembly
  • Camel :: Catalog :: CSimple Maven Plugin (deprecated)
  • Camel :: Catalog :: Camel Catalog
  • Camel :: Catalog :: Camel Report Maven Plugin
  • Camel :: Catalog :: Camel Route Parser
  • Camel :: Catalog :: Console
  • Camel :: Catalog :: Dummy Component
  • Camel :: Catalog :: Lucene (deprecated)
  • Camel :: Catalog :: Maven
  • Camel :: Catalog :: Suggest
  • Camel :: Component DSL
  • Camel :: Coverage
  • Camel :: Docs
  • Camel :: Endpoint DSL
  • Camel :: Endpoint DSL :: Support
  • Camel :: Integration Tests
  • Camel :: JBang :: Core
  • Camel :: JBang :: Integration tests
  • Camel :: JBang :: MCP
  • Camel :: JBang :: Main
  • Camel :: JBang :: Plugin :: Edit
  • Camel :: JBang :: Plugin :: Generate
  • Camel :: JBang :: Plugin :: Kubernetes
  • Camel :: JBang :: Plugin :: MCP
  • Camel :: JBang :: Plugin :: Route Parser
  • Camel :: JBang :: Plugin :: TUI
  • Camel :: JBang :: Plugin :: Testing
  • Camel :: JBang :: Plugin :: Validate
  • Camel :: JTA
  • Camel :: Kafka
  • Camel :: Kamelet Main
  • Camel :: Launcher
  • Camel :: Launcher :: Container
  • Camel :: SQL
  • Camel :: YAML DSL
  • Camel :: YAML DSL :: Deserializers
  • Camel :: YAML DSL :: Maven Plugins
  • Camel :: YAML DSL :: Validator
  • Camel :: YAML DSL :: Validator Maven Plugin

⚙️ View full build and test results

@Croway Croway merged commit e7bed57 into apache:main Jul 13, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants