blob: e50a5c30b21935c0496b6a84a5eaf90d315af9b4 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
part of core;
class _MojoHandleWatcherNatives {
static int sendControlData(
int controlHandle, int mojoHandle, SendPort port, int data)
native "MojoHandleWatcher_SendControlData";
static List recvControlData(int controlHandle)
native "MojoHandleWatcher_RecvControlData";
static int setControlHandle(int controlHandle)
native "MojoHandleWatcher_SetControlHandle";
static int getControlHandle()
native "MojoHandleWatcher_GetControlHandle";
}
// The MojoHandleWatcher sends a stream of events to application isolates that
// register Mojo handles with it. Application isolates make the following calls:
//
// Start() - Starts up the MojoHandleWatcher isolate. Should be called only once
// per VM process.
//
// Stop() - Causes the MojoHandleWatcher isolate to exit.
//
// add(handle, port, signals) - Instructs the MojoHandleWatcher isolate to add
// 'handle' to the set of handles it watches, and to notify the calling
// isolate only for the events specified by 'signals' using the send port
// 'port'
//
// remove(handle) - Instructs the MojoHandleWatcher isolate to remove 'handle'
// from the set of handles it watches. This allows the application isolate
// to, e.g., pause the stream of events.
//
// toggleWrite(handle) - Modifies the set of signals that an application isolate
// wishes to be notified about. Whether the application isolate should be
// be notified about a handle ready for writing is made the opposite.
//
// close(handle) - Notifies the HandleWatcherIsolate that a handle it is
// watching should be removed from its set and closed.
class MojoHandleWatcher {
// Control commands.
static const int ADD = 0;
static const int REMOVE = 1;
static const int TOGGLE_WRITE = 2;
static const int CLOSE = 3;
static const int SHUTDOWN = 4;
static int _encodeCommand(int cmd, [int signals = 0]) =>
(cmd << 2) | (signals & MojoHandleSignals.READWRITE);
static int _decodeCommand(int cmd) => cmd >> 2;
// The Mojo handle over which control messages are sent.
int _controlHandle;
// Whether the handle watcher should shut down.
bool _shutdown;
// The list of handles being watched.
List<int> _handles;
int _handleCount;
// A port for each handle on which to send events back to the isolate that
// owns the handle.
List<SendPort> _ports;
// The signals that we care about for each handle.
List<int> _signals;
// A mapping from Mojo handles to their indices in _handles.
Map<int, int> _handleIndices;
// Since we are not storing wrapped handles, a dummy handle for when we need
// a RawMojoHandle.
RawMojoHandle _tempHandle;
MojoHandleWatcher(this._controlHandle) :
_shutdown = false,
_handles = new List<int>(),
_ports = new List<SendPort>(),
_signals = new List<int>(),
_handleIndices = new Map<int, int>(),
_handleCount = 1,
_tempHandle = new RawMojoHandle(RawMojoHandle.INVALID) {
// Setup control handle.
_handles.add(_controlHandle);
_ports.add(null); // There is no port for the control handle.
_signals.add(MojoHandleSignals.READABLE);
_handleIndices[_controlHandle] = 0;
}
static void _handleWatcherIsolate(MojoHandleWatcher watcher) {
while (!watcher._shutdown) {
int res = RawMojoHandle.waitMany(watcher._handles,
watcher._signals,
RawMojoHandle.DEADLINE_INDEFINITE);
if (res == 0) {
watcher._handleControlMessage();
} else if (res > 0) {
int handle = watcher._handles[res];
// Route event.
watcher._routeEvent(res);
// Remove the handle from the list.
watcher._removeHandle(handle);
} else if (res == MojoResult.kFailedPrecondition) {
// None of the handles can ever be satisfied, including the control
// handle. This probably means we are going down. Clean up and
// shutdown.
watcher._pruneClosedHandles();
watcher._shutdown = true;
} else {
// Some handle was closed, but not by us.
// We have to go through the list and find it.
watcher._pruneClosedHandles();
}
}
}
void _routeEvent(int idx) {
int client_handle = _handles[idx];
int signals = _signals[idx];
SendPort port = _ports[idx];
_tempHandle.h = client_handle;
bool readyWrite =
MojoHandleSignals.isWritable(signals) && _tempHandle.readyWrite();
bool readyRead =
MojoHandleSignals.isReadable(signals) && _tempHandle.readyRead();
if (readyRead && readyWrite) {
port.send(MojoHandleSignals.READWRITE);
} else if (readyWrite) {
port.send(MojoHandleSignals.WRITABLE);
} else if (readyRead) {
port.send(MojoHandleSignals.READABLE);
}
_tempHandle.h = RawMojoHandle.INVALID;
}
void _handleControlMessage() {
List result = _MojoHandleWatcherNatives.recvControlData(_controlHandle);
// result[0] = mojo handle if any
// result[1] = SendPort if any
// result[2] = command << 2 | WRITABLE | READABLE
int signals = result[2] & MojoHandleSignals.READWRITE;
int command = _decodeCommand(result[2]);
switch (command) {
case ADD:
_addHandle(result[0], result[1], signals);
break;
case REMOVE:
_removeHandle(result[0]);
break;
case TOGGLE_WRITE:
_toggleWrite(result[0]);
break;
case CLOSE:
_close(result[0]);
break;
case SHUTDOWN:
_shutdownHandleWatcher();
break;
default:
throw new Exception("Invalid Command: $command");
break;
}
}
void _addHandle(int mojoHandle, SendPort port, int signals) {
_handles.add(mojoHandle);
_ports.add(port);
_signals.add(signals & MojoHandleSignals.READWRITE);
_handleIndices[mojoHandle] = _handleCount;
_handleCount++;
}
void _removeHandle(int mojoHandle) {
int idx = _handleIndices[mojoHandle];
if (idx == null) {
throw new Exception("Remove on a non-existent handle: idx = $idx.");
}
if (idx == 0) {
throw new Exception("The control handle (idx = 0) cannot be removed.");
}
// We don't use List.removeAt so that we know how to fix-up _handleIndices.
if (idx == _handleCount - 1) {
int handle = _handles[idx];
_handleIndices[handle] = null;
_handles.removeLast();
_signals.removeLast();
_ports.removeLast();
_handleCount--;
} else {
int last = _handleCount - 1;
_handles[idx] = _handles[last];
_signals[idx] = _signals[last];
_ports[idx] = _ports[last];
_handles.removeLast();
_signals.removeLast();
_ports.removeLast();
_handleIndices[_handles[idx]] = idx;
_handleCount--;
}
}
void _close(int mojoHandle) {
int idx = _handleIndices[mojoHandle];
if (idx == null) {
throw new Exception("Close on a non-existent handle: idx = $idx.");
}
if (idx == 0) {
throw new Exception("The control handle (idx = 0) cannot be closed.");
}
_tempHandle.h = _handles[idx];
_tempHandle.close();
_tempHandle.h = RawMojoHandle.INVALID;
_removeHandle(mojoHandle);
}
void _toggleWrite(int mojoHandle) {
int idx = _handleIndices[mojoHandle];
if (idx == null) {
throw new Exception("Toggle write on a non-existent handle: idx = $idx.");
}
if (idx == 0) {
throw new Exception("The control handle (idx = 0) cannot be toggled.");
}
_signals[idx] = MojoHandleSignals.toggleWrite(_signals[idx]);
}
void _pruneClosedHandles() {
List<int> closed = new List();
for (var h in _handles) {
_tempHandle.h = h;
MojoResult res = _tempHandle.wait(MojoHandleSignals.READWRITE, 0);
if ((!res.isOk) && (!res.isDeadlineExceeded)) {
closed.add(h);
}
_tempHandle.h = RawMojoHandle.INVALID;
}
for (var h in closed) {
_close(h);
}
}
void _shutdownHandleWatcher() {
_shutdown = true;
_tempHandle.h = _controlHandle;
_tempHandle.close();
_tempHandle.h = RawMojoHandle.INVALID;
}
static MojoResult _sendControlData(RawMojoHandle mojoHandle,
SendPort port,
int data) {
int controlHandle = _MojoHandleWatcherNatives.getControlHandle();
if (controlHandle == RawMojoHandle.INVALID) {
throw new Exception("Found invalid control handle");
}
int rawHandle = RawMojoHandle.INVALID;
if (mojoHandle != null) {
rawHandle = mojoHandle.h;
}
var result = _MojoHandleWatcherNatives.sendControlData(
controlHandle, rawHandle, port, data);
return new MojoResult(result);
}
static Future<Isolate> Start() {
// 5. Return Future<bool> giving true on success.
// Make a control message pipe,
MojoMessagePipe pipe = new MojoMessagePipe();
int consumerHandle = pipe.endpoints[0].handle.h;
int producerHandle = pipe.endpoints[1].handle.h;
// Make a MojoHandleWatcher with one end.
MojoHandleWatcher watcher = new MojoHandleWatcher(consumerHandle);
// Call setControlHandle with the other end.
_MojoHandleWatcherNatives.setControlHandle(producerHandle);
// Spawn the handle watcher isolate with the MojoHandleWatcher,
return Isolate.spawn(_handleWatcherIsolate, watcher);
}
static void Stop() {
// Send the shutdown command.
_sendControlData(null, null, _encodeCommand(SHUTDOWN));
// Close the control handle.
int controlHandle = _MojoHandleWatcherNatives.getControlHandle();
var handle = new RawMojoHandle(controlHandle);
handle.close();
}
static MojoResult close(RawMojoHandle mojoHandle) {
return _sendControlData(mojoHandle, null, _encodeCommand(CLOSE));
}
static MojoResult toggleWrite(RawMojoHandle mojoHandle) {
return _sendControlData(mojoHandle, null, _encodeCommand(TOGGLE_WRITE));
}
static MojoResult add(RawMojoHandle mojoHandle, SendPort port, int signals) {
return _sendControlData(mojoHandle, port, _encodeCommand(ADD, signals));
}
static MojoResult remove(RawMojoHandle mojoHandle) {
return _sendControlData(mojoHandle, null, _encodeCommand(REMOVE));
}
}