Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2015, the Dartino project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dartino project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE.md file. | 3 // BSD-style license that can be found in the LICENSE.md file. |
| 4 | 4 |
| 5 library fletchc.hub_main; | 5 library dartino_compiler.hub_main; |
| 6 | 6 |
| 7 import 'dart:collection' show | 7 import 'dart:collection' show |
| 8 Queue; | 8 Queue; |
| 9 | 9 |
| 10 import 'dart:io' hide | 10 import 'dart:io' hide |
| 11 exitCode, | 11 exitCode, |
| 12 stderr, | 12 stderr, |
| 13 stdin, | 13 stdin, |
| 14 stdout; | 14 stdout; |
| 15 | 15 |
| (...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 126 int length = view.getUint32(offset, commandEndianness); | 126 int length = view.getUint32(offset, commandEndianness); |
| 127 offset += 4; | 127 offset += 4; |
| 128 argv.add(UTF8.decode(toUint8ListView(view, offset, length))); | 128 argv.add(UTF8.decode(toUint8ListView(view, offset, length))); |
| 129 offset += length; | 129 offset += length; |
| 130 } | 130 } |
| 131 return argv; | 131 return argv; |
| 132 } | 132 } |
| 133 } | 133 } |
| 134 | 134 |
| 135 // Class for sending client commands from the hub (main isolate) to the | 135 // Class for sending client commands from the hub (main isolate) to the |
| 136 // fletch c++ client. | 136 // dartino c++ client. |
| 137 class ClientCommandSender extends CommandSender { | 137 class ClientCommandSender extends CommandSender { |
| 138 final Sink<List<int>> sink; | 138 final Sink<List<int>> sink; |
| 139 | 139 |
| 140 ClientCommandSender(this.sink); | 140 ClientCommandSender(this.sink); |
| 141 | 141 |
| 142 void sendExitCode(int exitCode) { | 142 void sendExitCode(int exitCode) { |
| 143 new CommandBuffer<ClientCommandCode>() | 143 new CommandBuffer<ClientCommandCode>() |
| 144 ..addUint32(exitCode) | 144 ..addUint32(exitCode) |
| 145 ..sendOn(sink, ClientCommandCode.ExitCode); | 145 ..sendOn(sink, ClientCommandCode.ExitCode); |
| 146 } | 146 } |
| 147 | 147 |
| 148 void sendDataCommand(ClientCommandCode code, List<int> data) { | 148 void sendDataCommand(ClientCommandCode code, List<int> data) { |
| 149 new CommandBuffer<ClientCommandCode>() | 149 new CommandBuffer<ClientCommandCode>() |
| 150 ..addUint32(data.length) | 150 ..addUint32(data.length) |
| 151 ..addUint8List(data) | 151 ..addUint8List(data) |
| 152 ..sendOn(sink, code); | 152 ..sendOn(sink, code); |
| 153 } | 153 } |
| 154 | 154 |
| 155 void sendClose() { | 155 void sendClose() { |
| 156 throwInternalError("Client (C++) doesn't support ClientCommandCode.Close."); | 156 throwInternalError("Client (C++) doesn't support ClientCommandCode.Close."); |
| 157 } | 157 } |
| 158 | 158 |
| 159 void sendEventLoopStarted() { | 159 void sendEventLoopStarted() { |
| 160 throwInternalError( | 160 throwInternalError( |
| 161 "Client (C++) doesn't support ClientCommandCode.EventLoopStarted."); | 161 "Client (C++) doesn't support ClientCommandCode.EventLoopStarted."); |
| 162 } | 162 } |
| 163 } | 163 } |
| 164 | 164 |
| 165 Future main(List<String> arguments) async { | 165 Future main(List<String> arguments) async { |
| 166 // When running this program, -Dfletch.version must be provided on the Dart | 166 // When running this program, -Ddartino.version must be provided on the Dart |
| 167 // VM command line. | 167 // VM command line. |
| 168 assert(const String.fromEnvironment('fletch.version') != null); | 168 assert(const String.fromEnvironment('dartino.version') != null); |
| 169 | 169 |
| 170 mainArguments.addAll(arguments); | 170 mainArguments.addAll(arguments); |
| 171 configFileUri = Uri.base.resolve(arguments.first); | 171 configFileUri = Uri.base.resolve(arguments.first); |
| 172 File configFile = new File.fromUri(configFileUri); | 172 File configFile = new File.fromUri(configFileUri); |
| 173 Directory tmpdir = Directory.systemTemp.createTempSync("fletch_client"); | 173 Directory tmpdir = Directory.systemTemp.createTempSync("dartino_client"); |
| 174 | 174 |
| 175 File socketFile = new File("${tmpdir.path}/socket"); | 175 File socketFile = new File("${tmpdir.path}/socket"); |
| 176 try { | 176 try { |
| 177 socketFile.deleteSync(); | 177 socketFile.deleteSync(); |
| 178 } on FileSystemException catch (e) { | 178 } on FileSystemException catch (e) { |
| 179 // Ignored. There's no way to check if a socket file exists. | 179 // Ignored. There's no way to check if a socket file exists. |
| 180 } | 180 } |
| 181 | 181 |
| 182 ServerSocket server; | 182 ServerSocket server; |
| 183 | 183 |
| (...skipping 143 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 327 } | 327 } |
| 328 session.hasActiveWorkerTask = true; | 328 session.hasActiveWorkerTask = true; |
| 329 return session.worker.performTask( | 329 return session.worker.performTask( |
| 330 combineTasks(initializer, task), clientConnection, userSession: session) | 330 combineTasks(initializer, task), clientConnection, userSession: session) |
| 331 .whenComplete(() { | 331 .whenComplete(() { |
| 332 session.hasActiveWorkerTask = false; | 332 session.hasActiveWorkerTask = false; |
| 333 }); | 333 }); |
| 334 } | 334 } |
| 335 } | 335 } |
| 336 | 336 |
| 337 /// Handles communication with the Fletch C++ client. | 337 /// Handles communication with the Dartino C++ client. |
| 338 class ClientConnection { | 338 class ClientConnection { |
| 339 /// Socket used for receiving and sending commands from/to the Fletch C++ | 339 /// Socket used for receiving and sending commands from/to the Dartino C++ |
| 340 /// client. | 340 /// client. |
| 341 final Socket socket; | 341 final Socket socket; |
| 342 | 342 |
| 343 /// Controller used to send commands to the from the ClientConnection to | 343 /// Controller used to send commands to the from the ClientConnection to |
| 344 /// anyone listening on ClientConnection.commands (see [commands] below). The | 344 /// anyone listening on ClientConnection.commands (see [commands] below). The |
| 345 /// only listener as of now is the WorkerConnection which typically forwards | 345 /// only listener as of now is the WorkerConnection which typically forwards |
| 346 /// the commands to the worker isolate. | 346 /// the commands to the worker isolate. |
| 347 final StreamController<ClientCommand> controller = | 347 final StreamController<ClientCommand> controller = |
| 348 new StreamController<ClientCommand>(); | 348 new StreamController<ClientCommand>(); |
| 349 | 349 |
| 350 final ClientLogger log; | 350 final ClientLogger log; |
| 351 | 351 |
| 352 /// The commandSender is used to send commands back to the Fletch C++ client. | 352 /// The commandSender is used to send commands back to the Dartino C++ client. |
| 353 ClientCommandSender commandSender; | 353 ClientCommandSender commandSender; |
| 354 | 354 |
| 355 StreamSubscription<ClientCommand> subscription; | 355 StreamSubscription<ClientCommand> subscription; |
| 356 Completer<Null> completer; | 356 Completer<Null> completer; |
| 357 | 357 |
| 358 Completer<List<String>> argumentsCompleter = new Completer<List<String>>(); | 358 Completer<List<String>> argumentsCompleter = new Completer<List<String>>(); |
| 359 | 359 |
| 360 /// The analysed version of the request from the client. | 360 /// The analysed version of the request from the client. |
| 361 /// Updated by [parseArguments]. | 361 /// Updated by [parseArguments]. |
| 362 AnalyzedSentence sentence; | 362 AnalyzedSentence sentence; |
| 363 | 363 |
| 364 /// Path to the fletch VM. Updated by [parseArguments]. | 364 /// Path to the dartino VM. Updated by [parseArguments]. |
| 365 String fletchVm; | 365 String dartinoVm; |
| 366 | 366 |
| 367 ClientConnection(this.socket, this.log); | 367 ClientConnection(this.socket, this.log); |
| 368 | 368 |
| 369 /// Stream of commands from the Fletch C++ client to the hub (main isolate). | 369 /// Stream of commands from the Dartino C++ client to the hub (main isolate). |
| 370 /// The commands are typically forwarded to a worker isolate, see | 370 /// The commands are typically forwarded to a worker isolate, see |
| 371 /// handleClientCommand. | 371 /// handleClientCommand. |
| 372 Stream<ClientCommand> get commands => controller.stream; | 372 Stream<ClientCommand> get commands => controller.stream; |
| 373 | 373 |
| 374 /// Completes when [endSession] is called. | 374 /// Completes when [endSession] is called. |
| 375 Future<Null> get done => completer.future; | 375 Future<Null> get done => completer.future; |
| 376 | 376 |
| 377 /// Completes with the command-line arguments from the client. | 377 /// Completes with the command-line arguments from the client. |
| 378 Future<List<String>> get arguments => argumentsCompleter.future; | 378 Future<List<String>> get arguments => argumentsCompleter.future; |
| 379 | 379 |
| 380 /// Start processing commands from the client. | 380 /// Start processing commands from the client. |
| 381 void start() { | 381 void start() { |
| 382 // Setup a command sender used to send responses from the hub (main isolate) | 382 // Setup a command sender used to send responses from the hub (main isolate) |
| 383 // back to the Fletch C++ client. | 383 // back to the Dartino C++ client. |
| 384 commandSender = new ClientCommandSender(socket); | 384 commandSender = new ClientCommandSender(socket); |
| 385 | 385 |
| 386 // Setup a listener for handling commands coming from the Fletch C++ | 386 // Setup a listener for handling commands coming from the Dartino C++ |
| 387 // client. | 387 // client. |
| 388 StreamTransformer<List<int>, ClientCommand> transformer = | 388 StreamTransformer<List<int>, ClientCommand> transformer = |
| 389 new ClientCommandTransformerBuilder().build(); | 389 new ClientCommandTransformerBuilder().build(); |
| 390 subscription = socket.transform(transformer).listen(null); | 390 subscription = socket.transform(transformer).listen(null); |
| 391 subscription | 391 subscription |
| 392 ..onData(handleClientCommand) | 392 ..onData(handleClientCommand) |
| 393 ..onError(handleClientCommandError) | 393 ..onError(handleClientCommandError) |
| 394 ..onDone(handleClientCommandsDone); | 394 ..onDone(handleClientCommandsDone); |
| 395 completer = new Completer<Null>(); | 395 completer = new Completer<Null>(); |
| 396 } | 396 } |
| (...skipping 22 matching lines...) Expand all Loading... | |
| 419 // Cancel the subscription if an error occurred, this prevents | 419 // Cancel the subscription if an error occurred, this prevents |
| 420 // [handleCommandsDone] from being called and attempt to complete | 420 // [handleCommandsDone] from being called and attempt to complete |
| 421 // [completer]. | 421 // [completer]. |
| 422 subscription.cancel(); | 422 subscription.cancel(); |
| 423 } | 423 } |
| 424 | 424 |
| 425 void handleClientCommandsDone() { | 425 void handleClientCommandsDone() { |
| 426 completer.complete(); | 426 completer.complete(); |
| 427 } | 427 } |
| 428 | 428 |
| 429 // Send a command back to the Fletch C++ client. | 429 // Send a command back to the Dartino C++ client. |
| 430 void sendCommandToClient(ClientCommand command) { | 430 void sendCommandToClient(ClientCommand command) { |
| 431 switch (command.code) { | 431 switch (command.code) { |
| 432 case ClientCommandCode.Stdout: | 432 case ClientCommandCode.Stdout: |
| 433 commandSender.sendStdoutBytes(command.data); | 433 commandSender.sendStdoutBytes(command.data); |
| 434 break; | 434 break; |
| 435 | 435 |
| 436 case ClientCommandCode.Stderr: | 436 case ClientCommandCode.Stderr: |
| 437 commandSender.sendStderrBytes(command.data); | 437 commandSender.sendStderrBytes(command.data); |
| 438 break; | 438 break; |
| 439 | 439 |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 472 } | 472 } |
| 473 } | 473 } |
| 474 commandSender.sendExitCode(exitCode); | 474 commandSender.sendExitCode(exitCode); |
| 475 endSession(); | 475 endSession(); |
| 476 } | 476 } |
| 477 | 477 |
| 478 AnalyzedSentence parseArguments(List<String> arguments) { | 478 AnalyzedSentence parseArguments(List<String> arguments) { |
| 479 Options options = Options.parse(arguments); | 479 Options options = Options.parse(arguments); |
| 480 Sentence sentence = | 480 Sentence sentence = |
| 481 parseSentence(options.nonOptionArguments, includesProgramName: true); | 481 parseSentence(options.nonOptionArguments, includesProgramName: true); |
| 482 // [programName] is the canonicalized absolute path to the fletch | 482 // [programName] is the canonicalized absolute path to the dartino |
| 483 // executable (the C++ program). | 483 // executable (the C++ program). |
| 484 String programName = sentence.programName; | 484 String programName = sentence.programName; |
| 485 String fletchVm = "$programName-vm"; | 485 String dartinoVm = "$programName-vm"; |
| 486 this.sentence = analyzeSentence(sentence, options); | 486 this.sentence = analyzeSentence(sentence, options); |
| 487 this.fletchVm = fletchVm; | 487 this.dartinoVm = dartinoVm; |
| 488 return this.sentence; | 488 return this.sentence; |
| 489 } | 489 } |
| 490 | 490 |
| 491 int reportErrorToClient(InputError error, StackTrace stackTrace) { | 491 int reportErrorToClient(InputError error, StackTrace stackTrace) { |
| 492 bool isInternalError = error.kind == DiagnosticKind.internalError; | 492 bool isInternalError = error.kind == DiagnosticKind.internalError; |
| 493 if (isInternalError && !crashReportRequested) { | 493 if (isInternalError && !crashReportRequested) { |
| 494 printLineOnStderr(requestBugReportOnOtherCrashMessage); | 494 printLineOnStderr(requestBugReportOnOtherCrashMessage); |
| 495 crashReportRequested = true; | 495 crashReportRequested = true; |
| 496 } | 496 } |
| 497 printLineOnStderr(error.asDiagnostic().formatMessage()); | 497 printLineOnStderr(error.asDiagnostic().formatMessage()); |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 511 final ManagedIsolate isolate; | 511 final ManagedIsolate isolate; |
| 512 | 512 |
| 513 /// A port used to send commands to the worker isolate. | 513 /// A port used to send commands to the worker isolate. |
| 514 SendPort sendPort; | 514 SendPort sendPort; |
| 515 | 515 |
| 516 /// A port used to read commands from the worker isolate. | 516 /// A port used to read commands from the worker isolate. |
| 517 ReceivePort receivePort; | 517 ReceivePort receivePort; |
| 518 | 518 |
| 519 /// workerCommands is an iterator over all the commands coming from the | 519 /// workerCommands is an iterator over all the commands coming from the |
| 520 /// worker isolate. These are typically the outbound messages destined for | 520 /// worker isolate. These are typically the outbound messages destined for |
| 521 /// the Fletch C++ client. | 521 /// the Dartino C++ client. |
| 522 /// It iterates over the data coming on the receivePort. | 522 /// It iterates over the data coming on the receivePort. |
| 523 StreamIterator<ClientCommand> workerCommands; | 523 StreamIterator<ClientCommand> workerCommands; |
| 524 | 524 |
| 525 /// When true, the worker can be shutdown by sending it a | 525 /// When true, the worker can be shutdown by sending it a |
| 526 /// ClientCommandCode.Signal command. Otherwise, it must be killed. | 526 /// ClientCommandCode.Signal command. Otherwise, it must be killed. |
| 527 bool eventLoopStarted = false; | 527 bool eventLoopStarted = false; |
| 528 | 528 |
| 529 /// Subscription for errors from [isolate]. | 529 /// Subscription for errors from [isolate]. |
| 530 StreamSubscription errorSubscription; | 530 StreamSubscription errorSubscription; |
| 531 | 531 |
| (...skipping 17 matching lines...) Expand all Loading... | |
| 549 // TODO(ahe): Add this assertion: assert(isolate.wasKilled); | 549 // TODO(ahe): Add this assertion: assert(isolate.wasKilled); |
| 550 endSession(); | 550 endSession(); |
| 551 return; | 551 return; |
| 552 } | 552 } |
| 553 ClientCommand command = workerCommands.current; | 553 ClientCommand command = workerCommands.current; |
| 554 assert(command.code == ClientCommandCode.SendPort); | 554 assert(command.code == ClientCommandCode.SendPort); |
| 555 assert(command.data != null); | 555 assert(command.data != null); |
| 556 sendPort = command.data; | 556 sendPort = command.data; |
| 557 } | 557 } |
| 558 | 558 |
| 559 /// Attach to a fletch C++ client and forward commands to the worker isolate, | 559 /// Attach to a dartino C++ client and forward commands to the worker isolate, |
| 560 /// and vice versa. The returned future normally completes when the worker | 560 /// and vice versa. The returned future normally completes when the worker |
| 561 /// isolate sends ClientCommandCode.ClosePort, or if the isolate is killed due | 561 /// isolate sends ClientCommandCode.ClosePort, or if the isolate is killed due |
| 562 /// to ClientCommandCode.Signal arriving through client.commands. | 562 /// to ClientCommandCode.Signal arriving through client.commands. |
| 563 Future<int> attachClient( | 563 Future<int> attachClient( |
| 564 ClientConnection clientConnection, | 564 ClientConnection clientConnection, |
| 565 UserSession userSession) async { | 565 UserSession userSession) async { |
| 566 | 566 |
| 567 // Method for handling commands coming from the client. The commands are | 567 // Method for handling commands coming from the client. The commands are |
| 568 // typically forwarded to the worker isolate. | 568 // typically forwarded to the worker isolate. |
| 569 handleCommandsFromClient(ClientCommand command) { | 569 handleCommandsFromClient(ClientCommand command) { |
| 570 if (command.code == ClientCommandCode.Signal && !eventLoopStarted) { | 570 if (command.code == ClientCommandCode.Signal && !eventLoopStarted) { |
| 571 if (userSession != null) { | 571 if (userSession != null) { |
| 572 userSession.kill(clientConnection.printLineOnStderr); | 572 userSession.kill(clientConnection.printLineOnStderr); |
| 573 } else { | 573 } else { |
| 574 isolate.kill(); | 574 isolate.kill(); |
| 575 } | 575 } |
| 576 receivePort.close(); | 576 receivePort.close(); |
| 577 } else { | 577 } else { |
| 578 sendPort.send([command.code.index, command.data]); | 578 sendPort.send([command.code.index, command.data]); |
| 579 } | 579 } |
| 580 } | 580 } |
| 581 | 581 |
| 582 // Method for handling commands coming back from the worker isolate. | 582 // Method for handling commands coming back from the worker isolate. |
| 583 // It typically forwards them to the Fletch C++ client via the | 583 // It typically forwards them to the Dartino C++ client via the |
| 584 // clientConnection. | 584 // clientConnection. |
| 585 Future<int> handleCommandsFromWorker( | 585 Future<int> handleCommandsFromWorker( |
| 586 ClientConnection clientConnection) async { | 586 ClientConnection clientConnection) async { |
| 587 int exitCode = COMPILER_EXITCODE_CRASH; | 587 int exitCode = COMPILER_EXITCODE_CRASH; |
| 588 while (await workerCommands.moveNext()) { | 588 while (await workerCommands.moveNext()) { |
| 589 ClientCommand command = workerCommands.current; | 589 ClientCommand command = workerCommands.current; |
| 590 switch (command.code) { | 590 switch (command.code) { |
| 591 case ClientCommandCode.ClosePort: | 591 case ClientCommandCode.ClosePort: |
| 592 receivePort.close(); | 592 receivePort.close(); |
| 593 break; | 593 break; |
| (...skipping 29 matching lines...) Expand all Loading... | |
| 623 } | 623 } |
| 624 if (userSession != null) { | 624 if (userSession != null) { |
| 625 userSession.kill(clientConnection.printLineOnStderr); | 625 userSession.kill(clientConnection.printLineOnStderr); |
| 626 } else { | 626 } else { |
| 627 isolate.kill(); | 627 isolate.kill(); |
| 628 } | 628 } |
| 629 receivePort.close(); | 629 receivePort.close(); |
| 630 }); | 630 }); |
| 631 errorSubscription.resume(); | 631 errorSubscription.resume(); |
| 632 | 632 |
| 633 // Start listening for commands coming from the Fletch C++ client (via | 633 // Start listening for commands coming from the Dartino C++ client (via |
| 634 // clientConnection). | 634 // clientConnection). |
| 635 // TODO(ahe): Add onDone event handler to detach the client. | 635 // TODO(ahe): Add onDone event handler to detach the client. |
| 636 clientConnection.commands.listen(handleCommandsFromClient); | 636 clientConnection.commands.listen(handleCommandsFromClient); |
| 637 | 637 |
| 638 // Start processing commands coming from the worker. | 638 // Start processing commands coming from the worker. |
| 639 int exitCode = await handleCommandsFromWorker(clientConnection); | 639 int exitCode = await handleCommandsFromWorker(clientConnection); |
| 640 | 640 |
| 641 errorSubscription.pause(); | 641 errorSubscription.pause(); |
| 642 return exitCode; | 642 return exitCode; |
| 643 } | 643 } |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 676 log.error(error, stackTrace); | 676 log.error(error, stackTrace); |
| 677 }).then((_) { | 677 }).then((_) { |
| 678 log.done(); | 678 log.done(); |
| 679 }); | 679 }); |
| 680 | 680 |
| 681 // Indirectly send the task to be performed to the worker isolate via the | 681 // Indirectly send the task to be performed to the worker isolate via the |
| 682 // clientConnection. | 682 // clientConnection. |
| 683 clientConnection.sendCommandToWorker( | 683 clientConnection.sendCommandToWorker( |
| 684 new ClientCommand(ClientCommandCode.PerformTask, task)); | 684 new ClientCommand(ClientCommandCode.PerformTask, task)); |
| 685 | 685 |
| 686 // Forward commands between the C++ fletch client [clientConnection], and th e | 686 // Forward commands between the C++ dartino client [clientConnection], and t he |
|
Søren Gjesse
2016/02/03 12:06:54
Long line.
ricow1
2016/02/03 12:29:18
Done.
| |
| 687 // worker isolate `this`. Also, Intercept the signal command and | 687 // worker isolate `this`. Also, Intercept the signal command and |
| 688 // potentially kill the isolate (the isolate needs to tell if it is | 688 // potentially kill the isolate (the isolate needs to tell if it is |
| 689 // interuptible or needs to be killed, an example of the latter is, if | 689 // interuptible or needs to be killed, an example of the latter is, if |
| 690 // compiler is running). | 690 // compiler is running). |
| 691 int exitCode = await attachClient(clientConnection, userSession); | 691 int exitCode = await attachClient(clientConnection, userSession); |
| 692 // The verb (which was performed in the worker) is done. | 692 // The verb (which was performed in the worker) is done. |
| 693 log.note("After attachClient (exitCode = $exitCode)"); | 693 log.note("After attachClient (exitCode = $exitCode)"); |
| 694 | 694 |
| 695 if (endSession) { | 695 if (endSession) { |
| 696 // Return the isolate to the pool *before* shutting down the client. This | 696 // Return the isolate to the pool *before* shutting down the client. This |
| (...skipping 152 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 849 | 849 |
| 850 void error(error, StackTrace stackTrace) { | 850 void error(error, StackTrace stackTrace) { |
| 851 // TODO(ahe): Modify quit verb to report these errors. | 851 // TODO(ahe): Modify quit verb to report these errors. |
| 852 erroneousClients.add(this); | 852 erroneousClients.add(this); |
| 853 note("Crash (${arguments.join(' ')}).\n" | 853 note("Crash (${arguments.join(' ')}).\n" |
| 854 "${stringifyError(error, stackTrace)}"); | 854 "${stringifyError(error, stackTrace)}"); |
| 855 } | 855 } |
| 856 | 856 |
| 857 String toString() => "$id"; | 857 String toString() => "$id"; |
| 858 } | 858 } |
| OLD | NEW |