Index: runtime/observatory/lib/src/service/object.dart |
diff --git a/runtime/observatory/lib/src/service/object.dart b/runtime/observatory/lib/src/service/object.dart |
index 925e0e75eba7d5cfaaadc88d4d4a2ee261ccb130..71c6a09682c5139f00514899c99a8d431b6a6e8e 100644 |
--- a/runtime/observatory/lib/src/service/object.dart |
+++ b/runtime/observatory/lib/src/service/object.dart |
@@ -364,16 +364,16 @@ abstract class VM extends ServiceObjectOwner { |
new StreamController.broadcast(); |
bool _isIsolateLifecycleEvent(String eventType) { |
- return _isIsolateShutdownEvent(eventType) || |
- _isIsolateCreatedEvent(eventType); |
+ return _isIsolateExitEvent(eventType) || |
+ _isIsolateStartEvent(eventType); |
} |
- bool _isIsolateShutdownEvent(String eventType) { |
- return (eventType == 'IsolateShutdown'); |
+ bool _isIsolateExitEvent(String eventType) { |
+ return (eventType == ServiceEvent.kIsolateExit); |
} |
- bool _isIsolateCreatedEvent(String eventType) { |
- return (eventType == 'IsolateCreated'); |
+ bool _isIsolateStartEvent(String eventType) { |
+ return (eventType == ServiceEvent.kIsolateStart); |
} |
void postServiceEvent(String response, ByteData data) { |
@@ -399,17 +399,17 @@ abstract class VM extends ServiceObjectOwner { |
if (_isIsolateLifecycleEvent(eventType)) { |
String isolateId = map['isolate']['id']; |
var event; |
- if (_isIsolateCreatedEvent(eventType)) { |
- _onIsolateCreated(map['isolate']); |
+ if (_isIsolateStartEvent(eventType)) { |
+ _onIsolateStart(map['isolate']); |
// By constructing the event *after* adding the isolate to the |
// isolate cache, the call to getFromMap will use the cached Isolate. |
event = new ServiceObject._fromMap(this, map); |
} else { |
- assert(_isIsolateShutdownEvent(eventType)); |
+ assert(_isIsolateExitEvent(eventType)); |
// By constructing the event *before* removing the isolate from the |
// isolate cache, the call to getFromMap will use the cached Isolate. |
event = new ServiceObject._fromMap(this, map); |
- _onIsolateShutdown(isolateId); |
+ _onIsolateExit(isolateId); |
} |
assert(event != null); |
events.add(event); |
@@ -426,11 +426,12 @@ abstract class VM extends ServiceObjectOwner { |
return; |
} |
var event = new ServiceObject._fromMap(owningIsolate, map); |
+ owningIsolate._onEvent(event); |
events.add(event); |
}); |
} |
- Isolate _onIsolateCreated(Map isolateMap) { |
+ Isolate _onIsolateStart(Map isolateMap) { |
var isolateId = isolateMap['id']; |
assert(!_isolateCache.containsKey(isolateId)); |
Isolate isolate = new ServiceObject._fromMap(this, isolateMap); |
@@ -443,7 +444,7 @@ abstract class VM extends ServiceObjectOwner { |
return isolate; |
} |
- void _onIsolateShutdown(String isolateId) { |
+ void _onIsolateExit(String isolateId) { |
assert(_isolateCache.containsKey(isolateId)); |
_isolateCache.remove(isolateId); |
notifyPropertyChange(#isolates, true, false); |
@@ -478,12 +479,12 @@ abstract class VM extends ServiceObjectOwner { |
// Process shutdown. |
for (var isolateId in shutdownIsolates) { |
- _onIsolateShutdown(isolateId); |
+ _onIsolateExit(isolateId); |
} |
// Process creation. |
for (var isolateMap in createdIsolates) { |
- _onIsolateCreated(isolateMap); |
+ _onIsolateStart(isolateMap); |
} |
} |
@@ -505,6 +506,10 @@ abstract class VM extends ServiceObjectOwner { |
// We should never see an unknown isolate here. |
throw new UnimplementedError(); |
} |
+ var mapIsRef = _hasRef(map['type']); |
+ if (!mapIsRef) { |
+ isolate.update(map); |
+ } |
return isolate; |
} |
@@ -774,11 +779,24 @@ class Isolate extends ServiceObjectOwner with Coverage { |
@observable ObservableMap counters = new ObservableMap(); |
@observable ServiceEvent pauseEvent = null; |
- bool get _isPaused => pauseEvent != null; |
+ void _updateRunState() { |
+ topFrame = (pauseEvent != null ? pauseEvent.topFrame : null); |
+ paused = (pauseEvent != null && |
+ pauseEvent.eventType != ServiceEvent.kResume); |
+ running = (!paused && topFrame != null); |
+ idle = (!paused && topFrame == null); |
+ notifyPropertyChange(#topFrame, 0, 1); |
+ notifyPropertyChange(#paused, 0, 1); |
+ notifyPropertyChange(#running, 0, 1); |
+ notifyPropertyChange(#idle, 0, 1); |
+ } |
+ |
+ @observable bool paused = false; |
@observable bool running = false; |
@observable bool idle = false; |
@observable bool loading = true; |
+ |
@observable bool ioEnabled = false; |
Map<String,ServiceObject> _cache = new Map<String,ServiceObject>(); |
@@ -844,7 +862,10 @@ class Isolate extends ServiceObjectOwner with Coverage { |
String mapId = map['id']; |
var obj = (mapId != null) ? _cache[mapId] : null; |
if (obj != null) { |
- // Consider calling update when map is not a reference. |
+ var mapIsRef = _hasRef(map['type']); |
+ if (!mapIsRef) { |
+ obj.update(map); |
+ } |
return obj; |
} |
// Build the object from the map directly. |
@@ -862,12 +883,7 @@ class Isolate extends ServiceObjectOwner with Coverage { |
Future<ServiceObject> invokeRpc(String method, Map params) { |
return invokeRpcNoUpgrade(method, params).then((ObservableMap response) { |
- var obj = new ServiceObject._fromMap(this, response); |
- if ((obj != null) && obj.canCache) { |
- String objId = obj.id; |
- _cache.putIfAbsent(objId, () => obj); |
- } |
- return obj; |
+ return getFromMap(response); |
}); |
} |
@@ -912,9 +928,11 @@ class Isolate extends ServiceObjectOwner with Coverage { |
@observable HeapSnapshot latestSnapshot; |
Completer<HeapSnapshot> _snapshotFetch; |
- void loadHeapSnapshot(ServiceEvent event) { |
+ void _loadHeapSnapshot(ServiceEvent event) { |
latestSnapshot = new HeapSnapshot(this, event.data); |
- _snapshotFetch.complete(latestSnapshot); |
+ if (_snapshotFetch != null) { |
+ _snapshotFetch.complete(latestSnapshot); |
+ } |
} |
Future<HeapSnapshot> fetchHeapSnapshot() { |
@@ -940,7 +958,6 @@ class Isolate extends ServiceObjectOwner with Coverage { |
_loaded = true; |
loading = false; |
- reloadBreakpoints(); |
_upgradeCollection(map, isolate); |
if (map['rootLib'] == null || |
map['timers'] == null || |
@@ -952,11 +969,6 @@ class Isolate extends ServiceObjectOwner with Coverage { |
if (map['entry'] != null) { |
entry = map['entry']; |
} |
- if (map['topFrame'] != null) { |
- topFrame = map['topFrame']; |
- } else { |
- topFrame = null ; |
- } |
var countersMap = map['tagCounters']; |
if (countersMap != null) { |
@@ -994,6 +1006,7 @@ class Isolate extends ServiceObjectOwner with Coverage { |
timers['dart'] = timerMap['time_dart_execution']; |
updateHeapsFromMap(map['heaps']); |
+ _updateBreakpoints(map['breakpoints']); |
List features = map['features']; |
if (features != null) { |
@@ -1003,10 +1016,8 @@ class Isolate extends ServiceObjectOwner with Coverage { |
} |
} |
} |
- // Isolate status |
pauseEvent = map['pauseEvent']; |
- running = (!_isPaused && map['topFrame'] != null); |
- idle = (!_isPaused && map['topFrame'] == null); |
+ _updateRunState(); |
error = map['error']; |
libraries.clear(); |
@@ -1023,71 +1034,74 @@ class Isolate extends ServiceObjectOwner with Coverage { |
}); |
} |
- ObservableList<Breakpoint> breakpoints = new ObservableList(); |
+ ObservableMap<int, Breakpoint> breakpoints = new ObservableMap(); |
- void _removeBreakpoint(Breakpoint bpt) { |
- var script = bpt.script; |
- var tokenPos = bpt.tokenPos; |
- assert(tokenPos != null); |
- if (script.loaded) { |
- var line = script.tokenToLine(tokenPos); |
- assert(line != null); |
- if (script.lines[line - 1] != null) { |
- assert(script.lines[line - 1].bpt == bpt); |
- script.lines[line - 1].bpt = null; |
+ void _updateBreakpoints(List newBpts) { |
+ // Build a map of new breakpoints. |
+ var newBptMap = {}; |
+ newBpts.forEach((bpt) => (newBptMap[bpt.number] = bpt)); |
+ |
+ // Remove any old breakpoints which no longer exist. |
+ List toRemove = []; |
+ breakpoints.forEach((key, _) { |
+ if (!newBptMap.containsKey(key)) { |
+ toRemove.add(key); |
} |
- } |
+ }); |
+ toRemove.forEach((key) => breakpoints.remove(key)); |
+ |
+ // Add all new breakpoints. |
+ breakpoints.addAll(newBptMap); |
} |
void _addBreakpoint(Breakpoint bpt) { |
- var script = bpt.script; |
- var tokenPos = bpt.tokenPos; |
- assert(tokenPos != null); |
- if (script.loaded) { |
- var line = script.tokenToLine(tokenPos); |
- assert(line != null); |
- assert(script.lines[line - 1].bpt == null); |
- script.lines[line - 1].bpt = bpt; |
- } else { |
- // Load the script and then plop in the breakpoint. |
- script.load().then((_) { |
- _addBreakpoint(bpt); |
- }); |
- } |
+ breakpoints[bpt.number] = bpt; |
} |
- void _updateBreakpoints(ServiceMap newBreakpoints) { |
- // Remove all of the old breakpoints from the Script lines. |
- if (breakpoints != null) { |
- for (var bpt in breakpoints) { |
- _removeBreakpoint(bpt); |
- } |
- } |
- // Add all of the new breakpoints to the Script lines. |
- for (var bpt in newBreakpoints['breakpoints']) { |
- _addBreakpoint(bpt); |
- } |
- breakpoints.clear(); |
- breakpoints.addAll(newBreakpoints['breakpoints']); |
- |
- // Sort the breakpoints by breakpointNumber. |
- breakpoints.sort((a, b) => (a.number - b.number)); |
+ void _removeBreakpoint(Breakpoint bpt) { |
+ breakpoints.remove(bpt.number); |
+ bpt.remove(); |
} |
- Future<ServiceObject> _inProgressReloadBpts; |
+ void _onEvent(ServiceEvent event) { |
+ assert(event.eventType != ServiceEvent.kIsolateStart && |
+ event.eventType != ServiceEvent.kIsolateExit); |
+ switch(event.eventType) { |
+ case ServiceEvent.kBreakpointAdded: |
+ _addBreakpoint(event.breakpoint); |
+ break; |
+ |
+ case ServiceEvent.kBreakpointResolved: |
+ // Update occurs as side-effect of caching. |
+ break; |
+ |
+ case ServiceEvent.kBreakpointRemoved: |
+ _removeBreakpoint(event.breakpoint); |
+ break; |
- Future reloadBreakpoints() { |
- // TODO(turnidge): Can reusing the Future here ever cause us to |
- // get stale breakpoints? |
- if (_inProgressReloadBpts == null) { |
- _inProgressReloadBpts = |
- invokeRpc('getBreakpoints', {}).then((newBpts) { |
- _updateBreakpoints(newBpts); |
- }).whenComplete(() { |
- _inProgressReloadBpts = null; |
- }); |
+ case ServiceEvent.kPauseStart: |
+ case ServiceEvent.kPauseExit: |
+ case ServiceEvent.kPauseBreakpoint: |
+ case ServiceEvent.kPauseInterrupted: |
+ case ServiceEvent.kPauseException: |
+ case ServiceEvent.kResume: |
+ pauseEvent = event; |
+ _updateRunState(); |
+ break; |
+ |
+ case ServiceEvent.kGraph: |
+ _loadHeapSnapshot(event); |
+ break; |
+ |
+ case ServiceEvent.kGC: |
+ // Ignore GC events for now. |
+ break; |
+ |
+ default: |
+ // Log unrecognized events. |
+ Logger.root.severe('Unrecognized event: $event'); |
+ break; |
} |
- return _inProgressReloadBpts; |
} |
Future<ServiceObject> addBreakpoint(Script script, int line) { |
@@ -1107,41 +1121,18 @@ class Isolate extends ServiceObjectOwner with Coverage { |
// Unable to set a breakpoint at desired line. |
script.lines[line - 1].possibleBpt = false; |
} |
- // TODO(turnidge): Instead of reloading all of the breakpoints, |
- // rely on events to update the breakpoint list. |
- return reloadBreakpoints().then((_) { |
- return result; |
- }); |
+ return result; |
}); |
} |
Future<ServiceObject> addBreakpointAtEntry(ServiceFunction function) { |
return invokeRpc('addBreakpointAtEntry', |
- { 'functionId': function.id }).then((result) { |
- // TODO(turnidge): Instead of reloading all of the breakpoints, |
- // rely on events to update the breakpoint list. |
- return reloadBreakpoints().then((_) { |
- return result; |
- }); |
- }); |
+ { 'functionId': function.id }); |
} |
Future removeBreakpoint(Breakpoint bpt) { |
return invokeRpc('removeBreakpoint', |
- { 'breakpointId': bpt.id }).then((result) { |
- if (result is DartError) { |
- // TODO(turnidge): Handle this more gracefully. |
- Logger.root.severe(result.message); |
- return result; |
- } |
- if (pauseEvent != null && |
- pauseEvent.breakpoint != null && |
- (pauseEvent.breakpoint.id == bpt.id)) { |
- return isolate.reload(); |
- } else { |
- return reloadBreakpoints(); |
- } |
- }); |
+ { 'breakpointId': bpt.id }); |
} |
// TODO(turnidge): If the user invokes pause (or other rpcs) twice, |
@@ -1319,14 +1310,10 @@ class ServiceMap extends ServiceObject implements ObservableMap { |
ServiceMap._empty(ServiceObjectOwner owner) : super._empty(owner); |
- void _upgradeValues() { |
- assert(owner != null); |
- _upgradeCollection(_map, owner); |
- } |
- |
void _update(ObservableMap map, bool mapIsRef) { |
_loaded = !mapIsRef; |
+ _upgradeCollection(map, owner); |
// TODO(turnidge): Currently _map.clear() prevents us from |
// upgrading an already upgraded submap. Is clearing really the |
// right thing to do here? |
@@ -1335,7 +1322,6 @@ class ServiceMap extends ServiceObject implements ObservableMap { |
name = _map['name']; |
vmName = (_map.containsKey('vmName') ? _map['vmName'] : name); |
- _upgradeValues(); |
} |
// Forward Map interface calls. |
@@ -1432,14 +1418,31 @@ class ServiceException extends ServiceObject { |
/// A [ServiceEvent] is an asynchronous event notification from the vm. |
class ServiceEvent extends ServiceObject { |
+ /// The possible 'eventType' values. |
+ static const kIsolateStart = 'IsolateStart'; |
+ static const kIsolateExit = 'IsolateExit'; |
+ static const kPauseStart = 'PauseStart'; |
+ static const kPauseExit = 'PauseExit'; |
+ static const kPauseBreakpoint = 'PauseBreakpoint'; |
+ static const kPauseInterrupted = 'PauseInterrupted'; |
+ static const kPauseException = 'PauseException'; |
+ static const kResume = 'Resume'; |
+ static const kBreakpointAdded = 'BreakpointAdded'; |
+ static const kBreakpointResolved = 'BreakpointResolved'; |
+ static const kBreakpointRemoved = 'BreakpointRemoved'; |
+ static const kGraph = '_Graph'; |
+ static const kGC = 'GC'; |
+ static const kVMDisconnected = 'VMDisconnected'; |
+ |
ServiceEvent._empty(ServiceObjectOwner owner) : super._empty(owner); |
ServiceEvent.vmDisconencted() : super._empty(null) { |
- eventType = 'VMDisconnected'; |
+ eventType = kVMDisconnected; |
} |
@observable String eventType; |
@observable Breakpoint breakpoint; |
+ @observable ServiceMap topFrame; |
@observable ServiceMap exception; |
@observable ByteData data; |
@observable int count; |
@@ -1453,6 +1456,9 @@ class ServiceEvent extends ServiceObject { |
if (map['breakpoint'] != null) { |
breakpoint = map['breakpoint']; |
} |
+ if (map['topFrame'] != null) { |
+ topFrame = map['topFrame']; |
+ } |
if (map['exception'] != null) { |
exception = map['exception']; |
} |
@@ -1465,8 +1471,12 @@ class ServiceEvent extends ServiceObject { |
} |
String toString() { |
- return 'ServiceEvent of type $eventType with ' |
- '${data == null ? 0 : data.lengthInBytes} bytes of binary data'; |
+ if (data == null) { |
+ return "ServiceEvent(owner='${owner.id}', type='${eventType}')"; |
+ } else { |
+ return "ServiceEvent(owner='${owner.id}', type='${eventType}', " |
+ "data.lengthInBytes=${data.lengthInBytes})"; |
+ } |
} |
} |
@@ -1488,19 +1498,44 @@ class Breakpoint extends ServiceObject { |
// The breakpoint has been assigned to a final source location. |
@observable bool resolved; |
- // The breakpoint is active. |
- @observable bool enabled; |
- |
void _update(ObservableMap map, bool mapIsRef) { |
_loaded = true; |
_upgradeCollection(map, owner); |
+ var newNumber = map['breakpointNumber']; |
+ var newScript = map['location']['script']; |
+ var newTokenPos = map['location']['tokenPos']; |
+ |
+ // number and script never change. |
+ assert((number == null) || (number == newNumber)); |
+ assert((script == null) || (script == newScript)); |
+ |
number = map['breakpointNumber']; |
script = map['location']['script']; |
- tokenPos = map['location']['tokenPos']; |
- |
resolved = map['resolved']; |
- enabled = map['enabled']; |
+ bool tokenPosChanged = tokenPos != newTokenPos; |
+ |
+ if (script.loaded && |
+ (tokenPos != null) && |
+ tokenPosChanged) { |
+ // The breakpoint has moved. Remove it and add it later. |
+ script._removeBreakpoint(this); |
+ } |
+ |
+ tokenPos = newTokenPos; |
+ if (script.loaded && tokenPosChanged) { |
+ script._addBreakpoint(this); |
+ } |
+ } |
+ |
+ void remove() { |
+ // Remove any references to this breakpoint. It has been removed. |
+ script._removeBreakpoint(this); |
+ if ((isolate.pauseEvent != null) && |
+ (isolate.pauseEvent.breakpoint != null) && |
+ (isolate.pauseEvent.breakpoint.id == id)) { |
+ isolate.pauseEvent.breakpoint = null; |
+ } |
} |
String toString() { |
@@ -1969,8 +2004,9 @@ class ScriptLine extends Observable { |
final int line; |
final String text; |
@observable int hits; |
- @observable Breakpoint bpt; |
@observable bool possibleBpt = true; |
+ @observable bool breakpointResolved = false; |
+ @observable Set<Breakpoint> breakpoints; |
bool get isBlank { |
// Compute isBlank on demand. |
@@ -2015,13 +2051,22 @@ class ScriptLine extends Observable { |
ScriptLine(this.script, this.line, this.text) { |
possibleBpt = !_isTrivialLine(text); |
+ } |
- // TODO(turnidge): This is not so efficient. Consider improving. |
- for (var bpt in this.script.isolate.breakpoints) { |
- if (bpt.script == this.script && |
- bpt.script.tokenToLine(bpt.tokenPos) == line) { |
- this.bpt = bpt; |
- } |
+ void addBreakpoint(Breakpoint bpt) { |
+ if (breakpoints == null) { |
+ breakpoints = new Set<Breakpoint>(); |
+ } |
+ breakpoints.add(bpt); |
+ breakpointResolved = breakpointResolved || bpt.resolved; |
+ } |
+ |
+ void removeBreakpoint(Breakpoint bpt) { |
+ assert(breakpoints != null && breakpoints.contains(bpt)); |
+ breakpoints.remove(bpt); |
+ if (breakpoints.isEmpty) { |
+ breakpoints = null; |
+ breakpointResolved = false; |
} |
} |
} |
@@ -2064,8 +2109,8 @@ class Script extends ServiceObject with Coverage { |
if (mapIsRef) { |
return; |
} |
- _processSource(map['source']); |
_parseTokenPosTable(map['tokenPosTable']); |
+ _processSource(map['source']); |
owningLibrary = map['owningLibrary']; |
} |
@@ -2145,6 +2190,12 @@ class Script extends ServiceObject with Coverage { |
for (var i = 0; i < sourceLines.length; i++) { |
lines.add(new ScriptLine(this, i + 1, sourceLines[i])); |
} |
+ for (var bpt in isolate.breakpoints.values) { |
+ if (bpt.script == this) { |
+ _addBreakpoint(bpt); |
+ } |
+ } |
+ |
_applyHitsToLines(); |
// Notify any Observers that this Script's state has changed. |
notifyChange(null); |
@@ -2156,6 +2207,18 @@ class Script extends ServiceObject with Coverage { |
line.hits = hits; |
} |
} |
+ |
+ void _addBreakpoint(Breakpoint bpt) { |
+ var line = tokenToLine(bpt.tokenPos); |
+ getLine(line).addBreakpoint(bpt); |
+ } |
+ |
+ void _removeBreakpoint(Breakpoint bpt) { |
+ var line = tokenToLine(bpt.tokenPos); |
+ if (line != null) { |
+ getLine(line).removeBreakpoint(bpt); |
+ } |
+ } |
} |
class PcDescriptor extends Observable { |