| // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| // @dart = 2.6 |
| |
| part of dart._vmservice; |
| |
| class RunningIsolate implements MessageRouter { |
| final int portId; |
| final SendPort sendPort; |
| final String name; |
| final Set<String> _resumeApprovalsByName = {}; |
| |
| RunningIsolate(this.portId, this.sendPort, this.name); |
| |
| String get serviceId => 'isolates/$portId'; |
| |
| static const kInvalidPauseEvent = -1; |
| static const kPauseOnStartMask = 1 << 0; |
| static const kPauseOnReloadMask = 1 << 1; |
| static const kPauseOnExitMask = 1 << 2; |
| static const kDefaultResumePermissionMask = |
| kPauseOnStartMask | kPauseOnReloadMask | kPauseOnExitMask; |
| |
| /// Resumes the isolate if all clients which need to approve a resume have |
| /// done so. Called when the last client of a given name disconnects or |
| /// changes name to ensure we don't deadlock waiting for approval to resume |
| /// from a disconnected client. |
| Future<void> maybeResumeAfterClientChange( |
| VMService service, String disconnectedClientName) async { |
| // Remove approvals from the disconnected client. |
| _resumeApprovalsByName.remove(disconnectedClientName); |
| |
| // If we've received approval to resume from all clients who care, clear |
| // approval state and resume. |
| var pauseType; |
| try { |
| pauseType = await _isolatePauseType(service, portId.toString()); |
| } catch (_errorResponse) { |
| // ignore errors when attempting to retrieve isolate pause type |
| return; |
| } |
| if (pauseType != kInvalidPauseEvent && |
| _shouldResume(service, null, pauseType)) { |
| _resumeApprovalsByName.clear(); |
| await Message.forMethod('resume') |
| ..params.addAll({ |
| 'isolateId': portId, |
| }) |
| ..sendToIsolate(sendPort); |
| } |
| } |
| |
| bool _shouldResume(VMService service, Client client, int pauseType) { |
| if (client != null) { |
| // Mark the approval by the client. |
| _resumeApprovalsByName.add(client.name); |
| } |
| final requiredClientApprovals = <String>{}; |
| final permissions = service.clientResumePermissions; |
| |
| // Determine which clients require approval for this pause type. |
| permissions.forEach((name, clientNamePermissions) { |
| if (clientNamePermissions.permissionsMask & pauseType != 0) { |
| requiredClientApprovals.add(name); |
| } |
| }); |
| |
| // We require at least a single client to resume, even if that client |
| // doesn't require resume approval. |
| if (_resumeApprovalsByName.isEmpty) { |
| return false; |
| } |
| |
| // If all the required approvals are present, we should resume. |
| return _resumeApprovalsByName.containsAll(requiredClientApprovals); |
| } |
| |
| Future<int> _isolatePauseType(VMService service, String isolateId) async { |
| final getIsolateMessage = Message.forMethod('getIsolate') |
| ..params.addAll({ |
| 'isolateId': isolateId, |
| }); |
| final Response result = await routeRequest(service, getIsolateMessage); |
| final resultJson = result.decodeJson(); |
| if (resultJson['result'] == null || |
| resultJson['result']['pauseEvent'] == null) { |
| // Failed to send getIsolate message(due to isolate being de-registered |
| // for example). |
| throw result; |
| } |
| final pauseEvent = resultJson['result']['pauseEvent']; |
| const pauseEvents = <String, int>{ |
| 'PauseStart': kPauseOnStartMask, |
| 'PausePostRequest': kPauseOnReloadMask, |
| 'PauseExit': kPauseOnExitMask, |
| }; |
| final kind = pauseEvent['kind']; |
| return pauseEvents[kind] ?? kInvalidPauseEvent; |
| } |
| |
| Future<Response> _routeResumeRequest( |
| VMService service, Message message) async { |
| // If we've received approval to resume from all clients who care, clear |
| // approval state and resume. |
| var pauseType; |
| try { |
| pauseType = await _isolatePauseType(service, message.params['isolateId']); |
| } catch (errorResponse) { |
| return errorResponse; |
| } |
| if (pauseType == kInvalidPauseEvent || |
| _shouldResume(service, message.client, pauseType)) { |
| _resumeApprovalsByName.clear(); |
| return message.sendToIsolate(sendPort); |
| } |
| |
| // We're still awaiting some approvals. Simply return success, but don't |
| // resume yet. |
| return Response(ResponsePayloadKind.String, encodeSuccess(message)); |
| } |
| |
| @override |
| Future<Response> routeRequest(VMService service, Message message) { |
| if (message.method == 'resume') { |
| return _routeResumeRequest(service, message); |
| } |
| // Send message to isolate. |
| return message.sendToIsolate(sendPort); |
| } |
| |
| @override |
| void routeResponse(Message message) {} |
| } |