Skip to content

ctlib: fix ct_results() hang on empty result set with CS_DYNAMIC_CMD#726

Open
tdaniel22 wants to merge 1 commit into
FreeTDS:masterfrom
tdaniel22:fix-ct-dynamic-empty-resultset
Open

ctlib: fix ct_results() hang on empty result set with CS_DYNAMIC_CMD#726
tdaniel22 wants to merge 1 commit into
FreeTDS:masterfrom
tdaniel22:fix-ct-dynamic-empty-resultset

Conversation

@tdaniel22
Copy link
Copy Markdown

When a prepared statement (CS_DYNAMIC_CMD) returns an empty result set, ct_results() hangs indefinitely in tds_process_tokens().

In 4fea9e7 ("fix ct-library rows count", 2016-04-30), CS_ROWFMT_RESULT was split from CS_COMPUTEFMT_RESULT into its own case, with an intentional fallthrough to CS_ROW_RESULT for non-cursor/non-dynamic commands. The CS_DYNAMIC_CMD check was added to break before the fallthrough.

Later, 4a8e777 (2016-05-11) restructured the CS_ROWFMT_RESULT case, replacing the fallthrough with an explicit process_flags update that adds TDS_STOPAT_DONE. However, the CS_DYNAMIC_CMD early break was preserved, now causing it to skip the process_flags update instead of just skipping a fallthrough.

Without TDS_STOPAT_DONE, ct_results() consumes the DONE token internally and returns a manufactured CS_ROW_RESULT. The caller then calls ct_fetch(), which calls tds_peek() to check the next token. Since the DONE token was already consumed and TDS state is IDLE, tds_peek() blocks forever on a socket read that will never produce data.

With rows present, TDS_STOPAT_ROW (already set) catches row tokens before the DONE, so ct_fetch sees the DONE via tds_peek and returns CS_END_DATA. Only empty result sets trigger the hang.

The fix removes CS_DYNAMIC_CMD from the early break so that process_flags is updated with TDS_STOPAT_DONE after CS_ROWFMT_RESULT, consistent with all other non-cursor command types.

Tested with SQL Anywhere 17 over TDS 5.0.

When a prepared statement (CS_DYNAMIC_CMD) returns an empty result set,
ct_results() hangs indefinitely in tds_process_tokens().

In 4fea9e7 ("fix ct-library rows count", 2016-04-30), CS_ROWFMT_RESULT
was split from CS_COMPUTEFMT_RESULT into its own case, with an intentional
fallthrough to CS_ROW_RESULT for non-cursor/non-dynamic commands. The
CS_DYNAMIC_CMD check was added to break before the fallthrough.

Later, 4a8e777 (2016-05-11) restructured the CS_ROWFMT_RESULT case,
replacing the fallthrough with an explicit process_flags update that adds
TDS_STOPAT_DONE. However, the CS_DYNAMIC_CMD early break was preserved,
now causing it to skip the process_flags update instead of just skipping
a fallthrough.

Without TDS_STOPAT_DONE, ct_results() consumes the DONE token internally
and returns a manufactured CS_ROW_RESULT. The caller then calls ct_fetch(),
which calls tds_peek() to check the next token. Since the DONE token was
already consumed and TDS state is IDLE, tds_peek() blocks forever on a
socket read that will never produce data.

With rows present, TDS_STOPAT_ROW (already set) catches row tokens before
the DONE, so ct_fetch sees the DONE via tds_peek and returns CS_END_DATA.
Only empty result sets trigger the hang.

The fix removes CS_DYNAMIC_CMD from the early break so that process_flags
is updated with TDS_STOPAT_DONE after CS_ROWFMT_RESULT, consistent with
all other non-cursor command types.

Tested with SQL Anywhere 17 over TDS 5.0.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant