Skip to content

Bad state: StreamSink is bound to a stream #5554

Closed
flutter/flutter
#171081

Description

@DanTup

While testing changes for #5552 I frequently see the following error when I trigger Save All:

Image

This message appears to be coming from the DA:

[2:58:02 PM] [DAP] [Info] ==> {"command":"hotReload","arguments":{"debounce":true,"onlyFlutter":true,"reason":"save"},"type":"request","seq":34}
[2:58:02 PM] [DAP] [Info] ==> {"command":"hotReload","arguments":{"debounce":true,"onlyFlutter":true,"reason":"save"},"type":"request","seq":35}
[2:58:02 PM] [DAP] [Info] ==> {"command":"hotReload","arguments":{"debounce":true,"onlyFlutter":true,"reason":"save"},"type":"request","seq":36}
[2:58:02 PM] [DAP] [Info] ==> {"command":"hotReload","arguments":{"debounce":true,"onlyFlutter":true,"reason":"save"},"type":"request","seq":37}
[2:58:02 PM] [DAP] [Info] <== {"seq":186,"type":"event","body":{"message":"Hot reloading…","progressId":"hotReload_26","title":"Flutter"},"event":"dart.progressStart"}
[2:58:02 PM] [DAP] [Info] ==> {"command":"hotReload","arguments":{"debounce":true,"onlyFlutter":true,"reason":"save"},"type":"request","seq":38}
[2:58:02 PM] [DAP] [Info] <== {"seq":187,"type":"event","body":{"message":"==> [Flutter] [{\"id\":25,\"method\":\"app.restart\",\"params\":{\"appId\":\"8551bca2-7d6f-42f9-8c8f-a8528f2e607f\",\"fullRestart\":false,\"pause\":true,\"reason\":\"save\",\"debounce\":true}}]\n"},"event":"dart.log"}
[2:58:02 PM] [DAP] [Info] <== {"seq":188,"type":"event","body":{"message":"Hot reloading…","progressId":"hotReload_27","title":"Flutter"},"event":"dart.progressStart"}
[2:58:02 PM] [DAP] [Info] <== {"seq":189,"type":"event","body":{"message":"==> [Flutter] [{\"id\":26,\"method\":\"app.restart\",\"params\":{\"appId\":\"8551bca2-7d6f-42f9-8c8f-a8528f2e607f\",\"fullRestart\":false,\"pause\":true,\"reason\":\"save\",\"debounce\":true}}]\n"},"event":"dart.log"}
[2:58:02 PM] [DAP] [Info] <== {"seq":190,"type":"event","body":{"progressId":"hotReload_27"},"event":"dart.progressEnd"}
[2:58:02 PM] [DAP] [Info] <== {"seq":191,"type":"response","body":{"error":{"format":"{message}","id":1,"variables":{"message":"Bad state: StreamSink is bound to a stream","stack":"#0      _StreamSinkImpl._controller (dart:io/io_sink.dart:235:7)\n#1      _StreamSinkImpl.add (dart:io/io_sink.dart:155:5)\n#2      _IOSinkImpl.write (dart:io/io_sink.dart:293:5)\n#3      _Socket.write (dart:io-patch/socket_patch.dart:2766:36)\n#4      _StdSink.writeln (dart:io/stdio.dart:442:13)\n#5      ProcessUtils._writeToStdinUnsafe.writeFlushAndComplete (package:flutter_tools/src/base/process.dart:288:15)\n#6      _rootRun (dart:async/zone.dart:1525:13)\n#7      _CustomZone.run (dart:async/zone.dart:1422:19)\n#8      _runZoned (dart:async/zone.dart:2034:6)\n#9      runZonedGuarded (dart:async/zone.dart:2020:12)\n#10     ProcessUtils._writeToStdinUnsafe (package:flutter_tools/src/base/process.dart:297:5)\n#11     ProcessUtils.writelnToStdinUnsafe (package:flutter_tools/src/base/process.dart:255:11)\n#12     FlutterDebugAdapter.sendFlutterMessage (package:flutter_tools/src/debug_adapters/flutter_adapter.dart:339:24)\n#13     FlutterDebugAdapter.sendFlutterRequest (package:flutter_tools/src/debug_adapters/flutter_adapter.dart:321:11)\n#14     FlutterDebugAdapter._performRestart (package:flutter_tools/src/debug_adapters/flutter_adapter.dart:684:13)\n#15     FlutterDebugAdapter.customRequest (package:flutter_tools/src/debug_adapters/flutter_adapter.dart:191:15)\n#16     BaseDebugAdapter.handle (package:dds/src/dap/base_debug_adapter.dart:141:20)\n#17     BaseDebugAdapter.handleIncomingRequest (package:dds/src/dap/base_debug_adapter.dart:477:7)\n#18     BaseDebugAdapter._handleIncomingMessage (package:dds/src/dap/base_debug_adapter.dart:311:7)\n#19     ByteStreamServerChannel._readMessage (package:dds/src/dap/protocol_stream.dart:82:18)\n#20     ByteStreamServerChannel.listen.<anonymous closure> (package:dds/src/dap/protocol_stream.dart:53:24)\n#21     _rootRunUnary (dart:async/zone.dart:1538:47)\n#22     _CustomZone.runUnary (dart:async/zone.dart:1429:19)\n#23     _CustomZone.runUnaryGuarded (dart:async/zone.dart:1329:7)\n#24     _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:381:11)\n#25     _DelayedData.perform (dart:async/stream_impl.dart:573:14)\n#26     _PendingEvents.handleNext (dart:async/stream_impl.dart:678:11)\n#27     _PendingEvents.schedule.<anonymous closure> (dart:async/stream_impl.dart:649:7)\n#28     _rootRun (dart:async/zone.dart:1517:47)\n#29     _CustomZone.run (dart:async/zone.dart:1422:19)\n#30     _CustomZone.runGuarded (dart:async/zone.dart:1321:7)\n#31     _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1362:23)\n#32     _rootRun (dart:async/zone.dart:1525:13)\n#33     _CustomZone.run (dart:async/zone.dart:1422:19)\n#34     _CustomZone.runGuarded (dart:async/zone.dart:1321:7)\n#35     _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1362:23)\n#36     _microtaskLoop (dart:async/schedule_microtask.dart:42:23)\n#37     _startMicrotaskLoop (dart:async/schedule_microtask.dart:60:5)\n#38     _runPendingImmediateCallback (dart:isolate-patch/isolate_patch.dart:127:13)\n#39     _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:194:5)\n"}}},"command":"hotReload","message":"Bad state: StreamSink is bound to a stream","request_seq":35,"success":false}
Bad state: StreamSink is bound to a stream
#0      _StreamSinkImpl._controller (dart:io/io_sink.dart:235:7)
#1      _StreamSinkImpl.add (dart:io/io_sink.dart:155:5)
#2      _IOSinkImpl.write (dart:io/io_sink.dart:293:5)
#3      _Socket.write (dart:io-patch/socket_patch.dart:2766:36)
#4      _StdSink.writeln (dart:io/stdio.dart:442:13)
#5      ProcessUtils._writeToStdinUnsafe.writeFlushAndComplete (package:flutter_tools/src/base/process.dart:288:15)
#6      _rootRun (dart:async/zone.dart:1525:13)
#7      _CustomZone.run (dart:async/zone.dart:1422:19)
#8      _runZoned (dart:async/zone.dart:2034:6)
#9      runZonedGuarded (dart:async/zone.dart:2020:12)
#10     ProcessUtils._writeToStdinUnsafe (package:flutter_tools/src/base/process.dart:297:5)
#11     ProcessUtils.writelnToStdinUnsafe (package:flutter_tools/src/base/process.dart:255:11)
#12     FlutterDebugAdapter.sendFlutterMessage (package:flutter_tools/src/debug_adapters/flutter_adapter.dart:339:24)
#13     FlutterDebugAdapter.sendFlutterRequest (package:flutter_tools/src/debug_adapters/flutter_adapter.dart:321:11)
#14     FlutterDebugAdapter._performRestart (package:flutter_tools/src/debug_adapters/flutter_adapter.dart:684:13)
#15     FlutterDebugAdapter.customRequest (package:flutter_tools/src/debug_adapters/flutter_adapter.dart:191:15)
#16     BaseDebugAdapter.handle (package:dds/src/dap/base_debug_adapter.dart:141:20)
#17     BaseDebugAdapter.handleIncomingRequest (package:dds/src/dap/base_debug_adapter.dart:477:7)
#18     BaseDebugAdapter._handleIncomingMessage (package:dds/src/dap/base_debug_adapter.dart:311:7)

Activity

  1. added this to the v3.114.0 milestone on Jun 18, 2025
  2. added
    in flutterRelates to running Flutter apps
    in debuggingRelates to the debug adapter or process of running debug sessions
    on Jun 18, 2025
  3. DanTup commented on Jun 18, 2025

    @DanTup
    MemberAuthor

    @bkonyi do you have any ideas about this? The call in DAP here sendFlutterMessage is writing to the stdin process of the flutter run --machine process that it spawned. It seems to fail in this way sometimes when multiple Hot Reloads are sent at the same time (whether this is because of the hot reload that's occurring, or the multiple writes in a small space of time, I'm not sure).

    The write goes through a method named ProcessUtils.writelnToStdinUnsafe, though it's not clear to me what's unsafe about it. Are we not allowed to call it again until the previous call as completed? If so, is there something better we can do than queueing every write to the process from the DAP? (it seems like anyone using ProcessUtils.writelnToStdinUnsafe would probably rather they queue than throw?).

  4. modified the milestones: v3.114.0, v3.116.0 on Jun 23, 2025
  5. bkonyi commented on Jun 23, 2025

    @bkonyi

    So ProcessUtils.writelnToStdinUnsafe calls stdin.flush() here which marks the sink as bound until the flush is complete. The returned future from writelnToStdinUnsafe completes when the flush operation is finished, but if we have an asynchronous gap and try to call writelnToStdinUnsafe again, it's possible to try and write to the sink while a flush() is in progress.

    If we're going to use ProcessUtils.writelnToStdinUnsafe, we're going to either need to use a mutex around stdin to ensure we only have a single writer to the sink at a given time or we'll have to make some sort of queue. I'm not 100% sure why we need this wrapper, so it could be that we can get away with just writing to stdin directly and not flushing.

  6. DanTup commented on Jun 24, 2025

    @DanTup
    MemberAuthor

    Oh, I think this is related to flutter/flutter#137184. Presumably without an explicit flush() then if it fails (eg. because the process terminates), it could throw an unhandled exception. The flush forces the caller to handle deal with that exception.

    So I think probably the best thing is for sendFlutterMessage() to simply queue each write on the end of the previous one. I feel like there are probably more callers writing to processes that might want this behaviour so I don't know if it's the right place to add it, but I guess I can add it here and it can always be extracted later if other places want this.

  7. DanTup commented on Jun 25, 2025

    @DanTup
    MemberAuthor

    This was fixed by flutter/flutter@7deecab.

  8. modified the milestones: v3.116.0, v3.114.0 on Jun 25, 2025
  9. added
    relies on sdk changesSomething that requires changes in the Dart/Flutter SDK to ship before it will become available
    on Jul 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    in debuggingRelates to the debug adapter or process of running debug sessionsin flutterRelates to running Flutter appsis bugrelies on sdk changesSomething that requires changes in the Dart/Flutter SDK to ship before it will become available

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions