Repository navigation
Conversation
…cal channel first When the consumer closes a LocalSinkChannel first, the channel's listener re-enters ShuffleSinkHandle.close(), whose second channel.close() returns false because the channel is already closed. The handle then skipped onFinish, so the fragment instance stayed in FLUSHING and kept its memory until the query terminator finished it. Treat a closed, non-aborted channel as closed. Cover both consumer-close orders, the remote path and aborted channels in MPPDataExchangeManagerTest.
5 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
A fragment instance (FI) of a query that has already succeeded can stay in
FLUSHINGwhen its consumer closes the local channel first. It keeps its admission memory untilSimpleQueryTerminatorfinishes it about one second later, so a query sent in that second can be rejected with719: There is not enough memory to execute current fragment instance.I found this through the intermittent 719s of
IoTDBLTTBTableFunctionITin #18725. With this one-line change on that PR's head, the class passed 8 runs in a row with-PTableSimpleIT, against 9 failing runs out of 12 without it. Its-PTableClusterITfailures have a second cause, which this change does not address.To check that it does not depend on that PR, I reproduced it on master
999721fcafwith a local IT (not part of this PR), without LTTB:sort_buffer_size_in_bytes = 3980000;SELECT part, count(s1) AS c, max(s1) AS m FROM regions GROUP BY part ORDER BY c DESC, part, whose root FI reserves 4,001,832 B;SELECT time, part, s1 FROM regions ORDER BY part, time, whose four region FIs and root need 15,950,312 B, so it is rejected only if something else still holds more than 139,725 B.At the first 719 of every master run, the memory held by other queries was the 4,001,832 B root FI of the preceding, already completed query, plus at most 9,352 B. (After that first rejection, master kept rejecting queries until the run ended, 89 to 295 of 300 per run; I have not looked into that part.)
Cause
LIMITstops reading (ExchangeOperator.java:144).LocalSourceHandle.close()→SharedTsBlockQueue.close()→LocalSinkChannel.close()(SharedTsBlockQueue.java:370).LocalSinkChannel.close()setsclosed = trueand then calls its listener (LocalSinkChannel.java:230,:233).ShuffleSinkHandle.close()(MPPDataExchangeManager.java:629,:635), which callschannel.close()again. That call returnsfalsebecause the channel is already closed (LocalSinkChannel.java:228), so the handle skipssinkListener.onFinish(this)(ShuffleSinkHandle.java:280,:291-:292).context.finished()(MPPDataExchangeManager.java:529) is not called, and the FI stays inFLUSHINGuntilSimpleQueryTerminator, after its 1000 ms grace period, callsFragmentInstanceManager.cancelTask, which callscontext.finished()for a query that did not fail (SimpleQueryTerminator.java:96-:97,:115;FragmentInstanceManager.java:411-:414).The remote
SinkChannel.close()callsinvokeOnFinished()before it setsclosed = true(SinkChannel.java:366-:367), so on that path the handle does finish.Change
A channel that is already closed and not aborted now counts as closed; an aborted channel still does not. The FI then reaches
FINISHEDthrough the samecontext.finished()call that the terminator makes a second later. When the consumer stops early, as with aLIMIT, the FI now finishes at that point, as it already does on the remote path: the producer's latersend()andsetNoMoreTsBlocks()return immediately, and its driver ends at the nextsink.isClosed()check (Driver.java:224).With several channels,
selfClosedstill takes the result of the last channel, as before; I left that unchanged. A mix of aborted and consumer-closed channels does not reach thisclose()through the channel listener, which skips it once any channel has aborted (MPPDataExchangeManager.java:631).Verification
MPPDataExchangeManagerTest, with the queue, channels, source handle andShuffleSinkHandlecreated throughMPPDataExchangeManagerand onlyFragmentInstanceContextmocked:isBlocked(), no sleeps);close()on the handle;ShuffleSinkHandle did not finish after the consumer closed ...; the other three pass on master too and guard the existing behaviour. All nine tests in the class pass with the change. Without!channel.isAborted(),testAbortedLocalChannelKeepsShuffleSinkHandleFromFinishingfails.queryengine/execution/exchangeandqueryengine/execution/fragmentpass (20 classes, 84 tests), and so dospotless:checkand the-P with-zh-localecompile.-PTableSimpleIT(140 tests, 33 of them@Ignored inIoTDBOrderByTableIT):IoTDBCollectFanInIT,IoTDBTableAggregationIT,IoTDBOrderByTableIT,IoTDBOrderByLimitOffsetAlignByDeviceTableIT,IoTDBAlignedOffsetLimitPushDownTableIT;-PSimpleIT(96 tests):IoTDBShuffleSink1IT,IoTDBShuffleSink2IT,IoTDBOrderByIT,IoTDBOrderByLimitOffsetAlignByDeviceIT,IoTDBAggregationIT;-PTableClusterIT(70 tests):IoTDBCollectFanInIT,IoTDBTableAggregationIT,IoTDBOrderByLimitOffsetAlignByDeviceTableIT.This PR has: