blob: b5a6e858f3eb86d91b948553954801f2b98cc8a7 [file] [log] [blame]
// 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.
library vmservice_io;
import 'dart:async';
import 'dart:collection';
import 'dart:convert';
import 'dart:io';
import 'dart:isolate';
import 'dart:typed_data';
import 'dart:_vmservice';
part 'loader.dart';
part 'server.dart';
// The TCP ip/port that the HTTP server listens on.
int _port;
String _ip;
// Should the HTTP server auto start?
bool _autoStart;
// Should the HTTP server run in devmode?
bool _originCheckDisabled;
bool _isWindows = false;
bool _isFuchsia = false;
var _signalWatch;
var _signalSubscription;
// HTTP server.
Server server;
Future<Server> serverFuture;
_lazyServerBoot() {
if (server != null) {
return;
}
// Lazily create service.
var service = new VMService();
// Lazily create server.
server = new Server(service, _ip, _port, _originCheckDisabled);
}
Future cleanupCallback() async {
shutdownLoaders();
// Cancel the sigquit subscription.
if (_signalSubscription != null) {
await _signalSubscription.cancel();
_signalSubscription = null;
}
if (server != null) {
try {
await server.cleanup(true);
} catch (e, st) {
print("Error in vm-service shutdown: $e\n$st\n");
}
}
if (_registerSignalHandlerTimer != null) {
_registerSignalHandlerTimer.cancel();
_registerSignalHandlerTimer = null;
}
// Call out to embedder's shutdown callback.
_shutdown();
}
Future<Uri> createTempDirCallback(String base) async {
Directory temp = await Directory.systemTemp.createTemp(base);
// Underneath the temporary directory, create a directory with the
// same name as the DevFS name [base].
var fsUri = temp.uri.resolveUri(new Uri.directory(base));
await new Directory.fromUri(fsUri).create();
return fsUri;
}
Future deleteDirCallback(Uri path) async {
Directory dir = new Directory.fromUri(path);
await dir.delete(recursive: true);
}
class PendingWrite {
PendingWrite(this.uri, this.bytes);
final Completer completer = new Completer();
final Uri uri;
final List<int> bytes;
Future write() async {
var file = new File.fromUri(uri);
var parent_directory = file.parent;
await parent_directory.create(recursive: true);
var result = await file.writeAsBytes(bytes);
completer.complete(null);
WriteLimiter._writeCompleted();
}
}
class WriteLimiter {
static final List<PendingWrite> pendingWrites = new List<PendingWrite>();
// non-rooted Android devices have a very low limit for the number of
// open files. Artificially cap ourselves to 16.
static const kMaxOpenWrites = 16;
static int openWrites = 0;
static Future scheduleWrite(Uri path, List<int> bytes) async {
// Create a new pending write.
PendingWrite pw = new PendingWrite(path, bytes);
pendingWrites.add(pw);
_maybeWriteFiles();
return pw.completer.future;
}
static _maybeWriteFiles() {
while (openWrites < kMaxOpenWrites) {
if (pendingWrites.length > 0) {
PendingWrite pw = pendingWrites.removeLast();
pw.write();
openWrites++;
} else {
break;
}
}
}
static _writeCompleted() {
openWrites--;
assert(openWrites >= 0);
_maybeWriteFiles();
}
}
Future writeFileCallback(Uri path, List<int> bytes) async {
return WriteLimiter.scheduleWrite(path, bytes);
}
Future writeStreamFileCallback(Uri path, Stream<List<int>> bytes) async {
var file = new File.fromUri(path);
var parent_directory = file.parent;
await parent_directory.create(recursive: true);
IOSink sink = await file.openWrite();
await sink.addStream(bytes);
await sink.close();
}
Future<List<int>> readFileCallback(Uri path) async {
var file = new File.fromUri(path);
return await file.readAsBytes();
}
Future<List<Map<String, dynamic>>> listFilesCallback(Uri dirPath) async {
var dir = new Directory.fromUri(dirPath);
var dirPathStr = dirPath.path;
var stream = dir.list(recursive: true);
var result = <Map<String, dynamic>>[];
await for (var fileEntity in stream) {
var filePath = new Uri.file(fileEntity.path).path;
var stat = await fileEntity.stat();
if (stat.type == FileSystemEntityType.FILE &&
filePath.startsWith(dirPathStr)) {
var map = <String, dynamic>{};
map['name'] = '/' + filePath.substring(dirPathStr.length);
map['size'] = stat.size;
map['modified'] = stat.modified.millisecondsSinceEpoch;
result.add(map);
}
}
return result;
}
Future<Uri> serverInformationCallback() async {
_lazyServerBoot();
return server.serverAddress;
}
Future<Uri> webServerControlCallback(bool enable) async {
_lazyServerBoot();
if (server.running == enable) {
// No change.
return server.serverAddress;
}
if (enable) {
await server.startup();
return server.serverAddress;
} else {
await server.shutdown(true);
return server.serverAddress;
}
}
Null _clearFuture(_) {
serverFuture = null;
}
_onSignal(ProcessSignal signal) {
if (serverFuture != null) {
// Still waiting.
return;
}
_lazyServerBoot();
// Toggle HTTP server.
if (server.running) {
serverFuture = server.shutdown(true).then(_clearFuture);
} else {
serverFuture = server.startup().then(_clearFuture);
}
}
Timer _registerSignalHandlerTimer;
_registerSignalHandler() {
_registerSignalHandlerTimer = null;
if (_signalWatch == null) {
// Cannot register for signals.
return;
}
if (_isWindows || _isFuchsia) {
// Cannot register for signals on Windows or Fuchsia.
return;
}
_signalSubscription = _signalWatch(ProcessSignal.SIGQUIT).listen(_onSignal);
}
@pragma("vm.entry-point", !const bool.fromEnvironment("dart.vm.product"))
main() {
// Set embedder hooks.
VMServiceEmbedderHooks.cleanup = cleanupCallback;
VMServiceEmbedderHooks.createTempDir = createTempDirCallback;
VMServiceEmbedderHooks.deleteDir = deleteDirCallback;
VMServiceEmbedderHooks.writeFile = writeFileCallback;
VMServiceEmbedderHooks.writeStreamFile = writeStreamFileCallback;
VMServiceEmbedderHooks.readFile = readFileCallback;
VMServiceEmbedderHooks.listFiles = listFilesCallback;
VMServiceEmbedderHooks.serverInformation = serverInformationCallback;
VMServiceEmbedderHooks.webServerControl = webServerControlCallback;
// Always instantiate the vmservice object so that the exit message
// can be delivered and waiting loaders can be cancelled.
new VMService();
if (_autoStart) {
_lazyServerBoot();
server.startup();
// It's just here to push an event on the event loop so that we invoke the
// scheduled microtasks.
Timer.run(() {});
}
scriptLoadPort.handler = _processLoadRequest;
// Register signal handler after a small delay to avoid stalling main
// isolate startup.
_registerSignalHandlerTimer = new Timer(shortDelay, _registerSignalHandler);
return scriptLoadPort;
}
_shutdown() native "VMServiceIO_Shutdown";