diff --git a/runtime/lib/isolate_patch.dart b/runtime/lib/isolate_patch.dart index 84928be491f..cd71ced68f4 100644 --- a/runtime/lib/isolate_patch.dart +++ b/runtime/lib/isolate_patch.dart @@ -335,6 +335,11 @@ patch class Isolate { static const _RESUME = 2; static const _PING = 3; static const _KILL = 4; + static const _ADD_EXIT = 5; + static const _DEL_EXIT = 6; + static const _ADD_ERROR = 7; + static const _DEL_ERROR = 8; + static const _ERROR_FATAL = 9; static void _spawnFunction(SendPort readyPort, Function topLevelFunction, @@ -367,15 +372,28 @@ patch class Isolate { } /* patch */ void addOnExitListener(SendPort responsePort) { - throw new UnsupportedError("addOnExitListener"); + var msg = new List(3) + ..[0] = 0 // Make room for OOB message type. + ..[1] = _ADD_EXIT + ..[2] = responsePort; + _sendOOB(controlPort, msg); } /* patch */ void removeOnExitListener(SendPort responsePort) { - throw new UnsupportedError("removeOnExitListener"); + var msg = new List(3) + ..[0] = 0 // Make room for OOB message type. + ..[1] = _DEL_EXIT + ..[2] = responsePort; + _sendOOB(controlPort, msg); } /* patch */ void setErrorsFatal(bool errorsAreFatal) { - throw new UnsupportedError("setErrorsFatal"); + var msg = new List(4) + ..[0] = 0 // Make room for OOB message type. + ..[1] = _ERROR_FATAL + ..[2] = terminateCapability + ..[3] = errorsAreFatal; + _sendOOB(controlPort, msg); } /* patch */ void kill([int priority = BEFORE_NEXT_EVENT]) { @@ -397,11 +415,19 @@ patch class Isolate { } /* patch */ void addErrorListener(SendPort port) { - throw new UnsupportedError("addErrorListener"); + var msg = new List(3) + ..[0] = 0 // Make room for OOB message type. + ..[1] = _ADD_ERROR + ..[2] = port; + _sendOOB(controlPort, msg); } /* patch */ void removeErrorListener(SendPort port) { - throw new UnsupportedError("removeErrorListener"); + var msg = new List(3) + ..[0] = 0 // Make room for OOB message type. + ..[1] = _DEL_ERROR + ..[2] = port; + _sendOOB(controlPort, msg); } static Isolate _getCurrentIsolate() { diff --git a/runtime/vm/isolate.cc b/runtime/vm/isolate.cc index a4b987fc680..d2a5f54b158 100644 --- a/runtime/vm/isolate.cc +++ b/runtime/vm/isolate.cc @@ -126,13 +126,21 @@ class IsolateMessageHandler : public MessageHandler { virtual Isolate* isolate() const { return isolate_; } private: - // Keep in sync with isolate_patch.dart. + // Keep both these enums in sync with isolate_patch.dart. + // The different Isolate API message types. enum { kPauseMsg = 1, kResumeMsg = 2, kPingMsg = 3, kKillMsg = 4, - + kAddExitMsg = 5, + kDelExitMsg = 6, + kAddErrorMsg = 7, + kDelErrorMsg = 8, + kErrorFatalMsg = 9, + }; + // The different Isolate API message priorities for ping and kill messages. + enum { kImmediateAction = 0, kBeforeNextEventAction = 1, kAsEventAction = 2 @@ -167,8 +175,8 @@ bool IsolateMessageHandler::HandleLibMessage(const Array& message) { if (message.Length() < 2) return true; const Object& type = Object::Handle(I, message.At(1)); if (!type.IsSmi()) return true; - const Smi& msg_type = Smi::Cast(type); - switch (msg_type.Value()) { + const intptr_t msg_type = Smi::Cast(type).Value(); + switch (msg_type) { case kPauseMsg: { // [ OOB, kPauseMsg, pause capability, resume capability ] if (message.Length() != 4) return true; @@ -255,6 +263,45 @@ bool IsolateMessageHandler::HandleLibMessage(const Array& message) { } break; } + case kAddExitMsg: + case kDelExitMsg: + case kAddErrorMsg: + case kDelErrorMsg: { + // [ OOB, msg, listener port ] + if (message.Length() != 3) return true; + const Object& obj = Object::Handle(I, message.At(2)); + if (!obj.IsSendPort()) return true; + const SendPort& listener = SendPort::Cast(obj); + switch (msg_type) { + case kAddExitMsg: + I->AddExitListener(listener); + break; + case kDelExitMsg: + I->RemoveExitListener(listener); + break; + case kAddErrorMsg: + I->AddErrorListener(listener); + break; + case kDelErrorMsg: + I->RemoveErrorListener(listener); + break; + default: + UNREACHABLE(); + } + break; + } + case kErrorFatalMsg: { + // [ OOB, kErrorFatalMsg, terminate capability, val ] + if (message.Length() != 4) return true; + // Check that the terminate capability has been passed correctly. + Object& obj = Object::Handle(I, message.At(2)); + if (!I->VerifyTerminateCapability(obj)) return true; + // Get the value to be set. + obj = message.At(3); + if (!obj.IsBool()) return true; + I->SetErrorsFatal(Bool::Cast(obj).value()); + break; + } #if defined(DEBUG) // Malformed OOB messages are silently ignored in release builds. default: @@ -438,8 +485,35 @@ bool IsolateMessageHandler::ProcessUnhandledException(const Error& result) { Dart_ExitScope(); } - I->object_store()->set_sticky_error(result); - return false; + // Generate the error and stacktrace strings for the error message. + String& exc_str = String::Handle(I); + String& stacktrace_str = String::Handle(I); + if (result.IsUnhandledException()) { + const UnhandledException& uhe = UnhandledException::Cast(result); + const Instance& exception = Instance::Handle(I, uhe.exception()); + Object& tmp = Object::Handle(I); + tmp = DartLibraryCalls::ToString(exception); + if (!tmp.IsString()) { + tmp = String::New(exception.ToCString()); + } + exc_str ^= tmp.raw(); + + const Instance& stacktrace = Instance::Handle(I, uhe.stacktrace()); + tmp = DartLibraryCalls::ToString(stacktrace); + if (!tmp.IsString()) { + tmp = String::New(stacktrace.ToCString()); + } + stacktrace_str ^= tmp.raw();; + } else { + exc_str = String::New(result.ToErrorCString()); + } + I->NotifyErrorListeners(exc_str, stacktrace_str); + + if (I->ErrorsFatal()) { + I->object_store()->set_sticky_error(result); + return false; + } + return true; } @@ -470,6 +544,7 @@ Isolate::Isolate() origin_id_(0), pause_capability_(0), terminate_capability_(0), + errors_fatal_(true), heap_(NULL), object_store_(NULL), top_exit_frame_info_(0), @@ -535,6 +610,7 @@ Isolate::Isolate(Isolate* original) main_port_(0), pause_capability_(0), terminate_capability_(0), + errors_fatal_(true), heap_(NULL), object_store_(NULL), top_exit_frame_info_(0), @@ -890,7 +966,7 @@ bool Isolate::VerifyTerminateCapability(const Object& capability) const { bool Isolate::AddResumeCapability(const Capability& capability) { // Ensure a limit for the number of resume capabilities remembered. - static const intptr_t kMaxResumeCapabilities = kSmiMax / (6*kWordSize); + static const intptr_t kMaxResumeCapabilities = kSmiMax / (6 * kWordSize); const GrowableObjectArray& caps = GrowableObjectArray::Handle( this, object_store()->resume_capabilities()); @@ -938,6 +1014,148 @@ bool Isolate::RemoveResumeCapability(const Capability& capability) { } +// TODO(iposva): Remove duplicated code and start using some hash based +// structure instead of these linear lookups. +void Isolate::AddExitListener(const SendPort& listener) { + // Ensure a limit for the number of listeners remembered. + static const intptr_t kMaxListeners = kSmiMax / (6 * kWordSize); + + const GrowableObjectArray& listeners = GrowableObjectArray::Handle( + this, object_store()->exit_listeners()); + SendPort& current = SendPort::Handle(this); + intptr_t insertion_index = -1; + for (intptr_t i = 0; i < listeners.Length(); i++) { + current ^= listeners.At(i); + if (current.IsNull()) { + if (insertion_index < 0) { + insertion_index = i; + } + } else if (current.Id() == listener.Id()) { + return; + } + } + if (insertion_index < 0) { + if (listeners.Length() >= kMaxListeners) { + // Cannot grow the array of listeners beyond its max. Additional + // listeners are ignored. In practice will never happen as we will + // run out of memory beforehand. + return; + } + listeners.Add(listener); + } else { + listeners.SetAt(insertion_index, listener); + } +} + + +void Isolate::RemoveExitListener(const SendPort& listener) { + const GrowableObjectArray& listeners = GrowableObjectArray::Handle( + this, object_store()->exit_listeners()); + SendPort& current = SendPort::Handle(this); + for (intptr_t i = 0; i < listeners.Length(); i++) { + current ^= listeners.At(i); + if (!current.IsNull() && (current.Id() == listener.Id())) { + // Remove the matching listener from the list. + current = SendPort::null(); + listeners.SetAt(i, current); + return; + } + } +} + + +void Isolate::NotifyExitListeners() { + const GrowableObjectArray& listeners = GrowableObjectArray::Handle( + this, this->object_store()->exit_listeners()); + if (listeners.IsNull()) return; + + SendPort& listener = SendPort::Handle(this); + for (intptr_t i = 0; i < listeners.Length(); i++) { + listener ^= listeners.At(i); + if (!listener.IsNull()) { + Dart_Port port_id = listener.Id(); + uint8_t* data = NULL; + intptr_t len = 0; + SerializeObject(Object::null_instance(), &data, &len, false); + Message* msg = new Message(port_id, data, len, Message::kNormalPriority); + PortMap::PostMessage(msg); + } + } +} + + +void Isolate::AddErrorListener(const SendPort& listener) { + // Ensure a limit for the number of listeners remembered. + static const intptr_t kMaxListeners = kSmiMax / (6 * kWordSize); + + const GrowableObjectArray& listeners = GrowableObjectArray::Handle( + this, object_store()->error_listeners()); + SendPort& current = SendPort::Handle(this); + intptr_t insertion_index = -1; + for (intptr_t i = 0; i < listeners.Length(); i++) { + current ^= listeners.At(i); + if (current.IsNull()) { + if (insertion_index < 0) { + insertion_index = i; + } + } else if (current.Id() == listener.Id()) { + return; + } + } + if (insertion_index < 0) { + if (listeners.Length() >= kMaxListeners) { + // Cannot grow the array of listeners beyond its max. Additional + // listeners are ignored. In practice will never happen as we will + // run out of memory beforehand. + return; + } + listeners.Add(listener); + } else { + listeners.SetAt(insertion_index, listener); + } +} + + +void Isolate::RemoveErrorListener(const SendPort& listener) { + const GrowableObjectArray& listeners = GrowableObjectArray::Handle( + this, object_store()->error_listeners()); + SendPort& current = SendPort::Handle(this); + for (intptr_t i = 0; i < listeners.Length(); i++) { + current ^= listeners.At(i); + if (!current.IsNull() && (current.Id() == listener.Id())) { + // Remove the matching listener from the list. + current = SendPort::null(); + listeners.SetAt(i, current); + return; + } + } +} + + +void Isolate::NotifyErrorListeners(const String& msg, + const String& stacktrace) { + const GrowableObjectArray& listeners = GrowableObjectArray::Handle( + this, this->object_store()->error_listeners()); + if (listeners.IsNull()) return; + + const Array& arr = Array::Handle(this, Array::New(2)); + arr.SetAt(0, msg); + arr.SetAt(1, stacktrace); + SendPort& listener = SendPort::Handle(this); + for (intptr_t i = 0; i < listeners.Length(); i++) { + listener ^= listeners.At(i); + if (!listener.IsNull()) { + Dart_Port port_id = listener.Id(); + uint8_t* data = NULL; + intptr_t len = 0; + SerializeObject(arr, &data, &len, false); + Message* msg = new Message(port_id, data, len, Message::kNormalPriority); + PortMap::PostMessage(msg); + } + } +} + + static void StoreError(Isolate* isolate, const Object& obj) { ASSERT(obj.IsError()); isolate->object_store()->set_sticky_error(Error::Cast(obj)); @@ -1170,6 +1388,11 @@ void Isolate::Shutdown() { StackZone stack_zone(this); HandleScope handle_scope(this); + // Notify exit listeners that this isolate is shutting down. + if (object_store() != NULL) { + NotifyExitListeners(); + } + // Clean up debugger resources. debugger()->Shutdown(); diff --git a/runtime/vm/isolate.h b/runtime/vm/isolate.h index 5d61b056025..5953fb28410 100644 --- a/runtime/vm/isolate.h +++ b/runtime/vm/isolate.h @@ -74,6 +74,7 @@ class RawFloat32x4; class RawInt32x4; class RawUserTag; class SampleBuffer; +class SendPort; class Simulator; class StackResource; class StackZone; @@ -410,6 +411,17 @@ class Isolate : public BaseIsolate { bool AddResumeCapability(const Capability& capability); bool RemoveResumeCapability(const Capability& capability); + void AddExitListener(const SendPort& listener); + void RemoveExitListener(const SendPort& listener); + void NotifyExitListeners(); + + void AddErrorListener(const SendPort& listener); + void RemoveErrorListener(const SendPort& listener); + void NotifyErrorListeners(const String& msg, const String& stacktrace); + + bool ErrorsFatal() const { return errors_fatal_; } + void SetErrorsFatal(bool val) { errors_fatal_ = val; } + Random* random() { return &random_; } Simulator* simulator() const { return simulator_; } @@ -674,6 +686,7 @@ class Isolate : public BaseIsolate { Dart_Port origin_id_; // Isolates created by spawnFunc have some origin id. uint64_t pause_capability_; uint64_t terminate_capability_; + bool errors_fatal_; Heap* heap_; ObjectStore* object_store_; uword top_exit_frame_info_; diff --git a/runtime/vm/object_store.cc b/runtime/vm/object_store.cc index 985f0b5a217..7ba0aba5d4c 100644 --- a/runtime/vm/object_store.cc +++ b/runtime/vm/object_store.cc @@ -75,6 +75,8 @@ ObjectStore::ObjectStore() pending_functions_(GrowableObjectArray::null()), pending_deferred_loads_(GrowableObjectArray::null()), resume_capabilities_(GrowableObjectArray::null()), + exit_listeners_(GrowableObjectArray::null()), + error_listeners_(GrowableObjectArray::null()), sticky_error_(Error::null()), empty_context_(Context::null()), stack_overflow_(Instance::null()), @@ -125,6 +127,8 @@ bool ObjectStore::PreallocateObjects() { this->pending_deferred_loads_ = GrowableObjectArray::New(); this->resume_capabilities_ = GrowableObjectArray::New(); + this->exit_listeners_ = GrowableObjectArray::New(); + this->error_listeners_ = GrowableObjectArray::New(); Object& result = Object::Handle(); const Library& library = Library::Handle(Library::CoreLibrary()); diff --git a/runtime/vm/object_store.h b/runtime/vm/object_store.h index 23dad67ea15..8d1519a58ff 100644 --- a/runtime/vm/object_store.h +++ b/runtime/vm/object_store.h @@ -342,6 +342,14 @@ class ObjectStore { return resume_capabilities_; } + RawGrowableObjectArray* exit_listeners() const { + return exit_listeners_; + } + + RawGrowableObjectArray* error_listeners() const { + return error_listeners_; + } + RawError* sticky_error() const { return sticky_error_; } void set_sticky_error(const Error& value) { ASSERT(!value.IsNull()); @@ -488,6 +496,8 @@ class ObjectStore { RawGrowableObjectArray* pending_functions_; RawGrowableObjectArray* pending_deferred_loads_; RawGrowableObjectArray* resume_capabilities_; + RawGrowableObjectArray* exit_listeners_; + RawGrowableObjectArray* error_listeners_; RawError* sticky_error_; RawContext* empty_context_; RawInstance* stack_overflow_; diff --git a/tests/isolate/handle_error2_test.dart b/tests/isolate/handle_error2_test.dart index 8a72a0e3f16..8f5df261598 100644 --- a/tests/isolate/handle_error2_test.dart +++ b/tests/isolate/handle_error2_test.dart @@ -47,7 +47,6 @@ Future spawn(entry) { main(){ asyncStart(); RawReceivePort reply = new RawReceivePort(null); - RawReceivePort reply2 = new RawReceivePort(null); // Create two isolates waiting for commands, with errors non-fatal. Future iso1 = spawn(isomain1); Future iso2 = spawn(isomain1); diff --git a/tests/isolate/handle_error3_test.dart b/tests/isolate/handle_error3_test.dart index f992df0164e..6e76625876f 100644 --- a/tests/isolate/handle_error3_test.dart +++ b/tests/isolate/handle_error3_test.dart @@ -92,7 +92,7 @@ main(){ case 2: Expect.equals(new RangeError.value(37).toString(), "$error"); state2++; - reply.close(); + reply2.close(); isolate2.removeErrorListener(errorPort2.sendPort); errorPort2.close(); asyncEnd(); diff --git a/tests/isolate/isolate.status b/tests/isolate/isolate.status index 30a218ee490..3eb1810f968 100644 --- a/tests/isolate/isolate.status +++ b/tests/isolate/isolate.status @@ -11,16 +11,9 @@ mandel_isolate_test: Skip # Uses 600 MB Ram on our 1 GB test device. [ $compiler == none || $compiler == dart2dart ] compile_time_error_test/01: Skip # Issue 12587 -ondone_test: Fail # Not implemented yet ping_test: Skip # Resolve test issues ping_pause_test: Skip # Resolve test issues -kill_test: Fail # Not implemented yet -kill2_test: Fail # Not implemented yet -kill3_test: Fail # Not implemented yet -kill_self_test: Fail # Not implemented yet -handle_error_test: Fail # Not implemented yet -handle_error2_test: Fail # Not implemented yet -handle_error3_test: Fail # Not implemented yet +kill3_test: Pass, Fail # Bad test: expects total message order message3_test/constList_identical: RuntimeError # Issue 21816 message3_test/constMap: RuntimeError # Issue 21816