|  | // Copyright (c) 2011, 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. | 
|  |  | 
|  | #include "include/dart_api.h" | 
|  | #include "include/dart_native_api.h" | 
|  |  | 
|  | #include "vm/unit_test.h" | 
|  |  | 
|  | // Custom Isolate Test. | 
|  | // | 
|  | // This mid-size test uses the Dart Embedding Api to create a custom | 
|  | // isolate abstraction.  Instead of having a dedicated thread for each | 
|  | // isolate, as is the case normally, this implementation shares a | 
|  | // single thread among the isolates using an event queue. | 
|  |  | 
|  | namespace dart { | 
|  |  | 
|  | DECLARE_FLAG(bool, trace_shutdown); | 
|  |  | 
|  | static void native_echo(Dart_NativeArguments args); | 
|  | static void CustomIsolateImpl_start(Dart_NativeArguments args); | 
|  | static Dart_NativeFunction NativeLookup(Dart_Handle name, | 
|  | int argc, | 
|  | bool* auto_setup_scope); | 
|  |  | 
|  | static const char* kCustomIsolateScriptChars = | 
|  | R"( | 
|  | import 'dart:isolate'; | 
|  |  | 
|  | final RawReceivePort mainPort = new RawReceivePort(); | 
|  | final SendPort mainSendPort = mainPort.sendPort; | 
|  |  | 
|  | @pragma('vm:external-name', 'native_echo') | 
|  | external void echo(arg); | 
|  |  | 
|  | class CustomIsolateImpl implements CustomIsolate { | 
|  | CustomIsolateImpl(String entry) : _entry = entry { | 
|  | echo('Constructing isolate'); | 
|  | } | 
|  |  | 
|  | SendPort spawn() { | 
|  | return _start(_entry); | 
|  | } | 
|  |  | 
|  | @pragma('vm:external-name', 'CustomIsolateImpl_start') | 
|  | external static SendPort _start(entry); | 
|  |  | 
|  | String _entry; | 
|  | } | 
|  |  | 
|  | abstract class CustomIsolate { | 
|  | factory CustomIsolate(String entry) = CustomIsolateImpl; | 
|  |  | 
|  | SendPort spawn(); | 
|  | } | 
|  |  | 
|  | isolateMain() { | 
|  | echo('Running isolateMain'); | 
|  | mainPort.handler = (message) { | 
|  | var data = message[0]; | 
|  | var replyTo = message[1]; | 
|  | echo('Received: $data'); | 
|  | replyTo.send(data + 1); | 
|  | mainPort.close(); | 
|  | }; | 
|  | } | 
|  |  | 
|  | main() { | 
|  | var isolate = new CustomIsolate("isolateMain"); | 
|  | var receivePort = new RawReceivePort(); | 
|  | SendPort port = isolate.spawn(); | 
|  | port.send([42, receivePort.sendPort]); | 
|  | receivePort.handler = (message) { | 
|  | receivePort.close(); | 
|  | echo('Received: $message'); | 
|  | }; | 
|  | return 'success'; | 
|  | } | 
|  | )"; | 
|  |  | 
|  | // An entry in our event queue. | 
|  | class Event { | 
|  | protected: | 
|  | explicit Event(Dart_Isolate isolate) : isolate_(isolate), next_(nullptr) {} | 
|  |  | 
|  | public: | 
|  | virtual ~Event() {} | 
|  | virtual void Process() = 0; | 
|  |  | 
|  | Dart_Isolate isolate() const { return isolate_; } | 
|  |  | 
|  | private: | 
|  | friend class EventQueue; | 
|  | Dart_Isolate isolate_; | 
|  | Event* next_; | 
|  | }; | 
|  |  | 
|  | // A simple event queue for our test. | 
|  | class EventQueue { | 
|  | public: | 
|  | EventQueue() { head_ = nullptr; } | 
|  |  | 
|  | void Add(Event* event) { | 
|  | if (head_ == nullptr) { | 
|  | head_ = event; | 
|  | tail_ = event; | 
|  | } else { | 
|  | tail_->next_ = event; | 
|  | tail_ = event; | 
|  | } | 
|  | } | 
|  |  | 
|  | Event* Get() { | 
|  | if (head_ == nullptr) { | 
|  | return nullptr; | 
|  | } | 
|  | Event* tmp = head_; | 
|  | head_ = head_->next_; | 
|  | if (head_ == nullptr) { | 
|  | // Not necessary, but why not. | 
|  | tail_ = nullptr; | 
|  | } | 
|  |  | 
|  | return tmp; | 
|  | } | 
|  |  | 
|  | void RemoveEventsForIsolate(Dart_Isolate isolate) { | 
|  | Event* cur = head_; | 
|  | Event* prev = nullptr; | 
|  | while (cur != nullptr) { | 
|  | Event* next = cur->next_; | 
|  | if (cur->isolate() == isolate) { | 
|  | // Remove matching event. | 
|  | if (prev != nullptr) { | 
|  | prev->next_ = next; | 
|  | } else { | 
|  | head_ = next; | 
|  | } | 
|  | delete cur; | 
|  | } else { | 
|  | // Advance. | 
|  | prev = cur; | 
|  | } | 
|  | cur = next; | 
|  | } | 
|  | tail_ = prev; | 
|  | } | 
|  |  | 
|  | private: | 
|  | Event* head_; | 
|  | Event* tail_; | 
|  | }; | 
|  | EventQueue* event_queue; | 
|  |  | 
|  | // Start an isolate. | 
|  | class StartEvent : public Event { | 
|  | public: | 
|  | StartEvent(Dart_Isolate isolate, const char* main) | 
|  | : Event(isolate), main_(main) {} | 
|  |  | 
|  | virtual void Process(); | 
|  |  | 
|  | private: | 
|  | const char* main_; | 
|  | }; | 
|  |  | 
|  | void StartEvent::Process() { | 
|  | OS::PrintErr(">> StartEvent with isolate(%p)--\n", isolate()); | 
|  | Dart_EnterIsolate(isolate()); | 
|  | Dart_EnterScope(); | 
|  | Dart_Handle result; | 
|  |  | 
|  | Dart_Handle lib = Dart_LookupLibrary(NewString(TestCase::url())); | 
|  | EXPECT_VALID(lib); | 
|  |  | 
|  | result = Dart_Invoke(lib, NewString(main_), 0, nullptr); | 
|  | EXPECT_VALID(result); | 
|  | free(const_cast<char*>(main_)); | 
|  | main_ = nullptr; | 
|  |  | 
|  | Dart_SetMessageNotifyCallback(nullptr); | 
|  | Dart_ExitScope(); | 
|  | Dart_ExitIsolate(); | 
|  | } | 
|  |  | 
|  | // Notify an isolate of a pending message. | 
|  | class MessageEvent : public Event { | 
|  | public: | 
|  | explicit MessageEvent(Dart_Isolate isolate) : Event(isolate) {} | 
|  |  | 
|  | ~MessageEvent() {} | 
|  |  | 
|  | virtual void Process(); | 
|  | }; | 
|  |  | 
|  | void MessageEvent::Process() { | 
|  | OS::PrintErr("$$ MessageEvent with isolate(%p)\n", isolate()); | 
|  | Dart_EnterIsolate(isolate()); | 
|  | Dart_EnterScope(); | 
|  |  | 
|  | Dart_Handle result = Dart_HandleMessage(); | 
|  | EXPECT_VALID(result); | 
|  |  | 
|  | if (!Dart_HasLivePorts()) { | 
|  | OS::PrintErr("<< Shutting down isolate(%p)\n", isolate()); | 
|  | event_queue->RemoveEventsForIsolate(isolate()); | 
|  | Dart_SetMessageNotifyCallback(nullptr); | 
|  | Dart_ExitScope(); | 
|  | Dart_ShutdownIsolate(); | 
|  | } else { | 
|  | Dart_ExitScope(); | 
|  | Dart_ExitIsolate(); | 
|  | } | 
|  | ASSERT(Dart_CurrentIsolate() == nullptr); | 
|  | } | 
|  |  | 
|  | static void NotifyMessage(Dart_Isolate dest_isolate) { | 
|  | OS::PrintErr("-- Notify isolate(%p) of pending message --\n", dest_isolate); | 
|  | OS::PrintErr("-- Adding MessageEvent to queue --\n"); | 
|  | event_queue->Add(new MessageEvent(dest_isolate)); | 
|  | } | 
|  |  | 
|  | static Dart_NativeFunction NativeLookup(Dart_Handle name, | 
|  | int argc, | 
|  | bool* auto_setup_scope) { | 
|  | ASSERT(auto_setup_scope != nullptr); | 
|  | *auto_setup_scope = true; | 
|  | const char* name_str = nullptr; | 
|  | EXPECT(Dart_IsString(name)); | 
|  | EXPECT_VALID(Dart_StringToCString(name, &name_str)); | 
|  | if (strcmp(name_str, "native_echo") == 0) { | 
|  | return &native_echo; | 
|  | } else if (strcmp(name_str, "CustomIsolateImpl_start") == 0) { | 
|  | return &CustomIsolateImpl_start; | 
|  | } | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | char* saved_echo = nullptr; | 
|  | static void native_echo(Dart_NativeArguments args) { | 
|  | Dart_EnterScope(); | 
|  | Dart_Handle arg = Dart_GetNativeArgument(args, 0); | 
|  | Dart_Handle toString = Dart_ToString(arg); | 
|  | EXPECT_VALID(toString); | 
|  | const char* c_str = nullptr; | 
|  | EXPECT_VALID(Dart_StringToCString(toString, &c_str)); | 
|  | if (saved_echo != nullptr) { | 
|  | free(saved_echo); | 
|  | } | 
|  | saved_echo = Utils::StrDup(c_str); | 
|  | OS::PrintErr("-- (isolate=%p) %s\n", Dart_CurrentIsolate(), c_str); | 
|  | Dart_ExitScope(); | 
|  | } | 
|  |  | 
|  | static void CustomIsolateImpl_start(Dart_NativeArguments args) { | 
|  | OS::PrintErr("-- Enter: CustomIsolateImpl_start --\n"); | 
|  |  | 
|  | // We would probably want to pass in the this pointer too, so we | 
|  | // could associate the CustomIsolateImpl instance with the | 
|  | // Dart_Isolate by storing it in a native field. | 
|  | EXPECT_EQ(1, Dart_GetNativeArgumentCount(args)); | 
|  | Dart_Handle param = Dart_GetNativeArgument(args, 0); | 
|  | EXPECT_VALID(param); | 
|  | EXPECT(Dart_IsString(param)); | 
|  | const char* isolate_main = nullptr; | 
|  | EXPECT_VALID(Dart_StringToCString(param, &isolate_main)); | 
|  | isolate_main = Utils::StrDup(isolate_main); | 
|  |  | 
|  | // Save current isolate. | 
|  | Dart_Isolate saved_isolate = Dart_CurrentIsolate(); | 
|  | Dart_ExitIsolate(); | 
|  |  | 
|  | // Create a new Dart_Isolate. | 
|  | Dart_Isolate new_isolate = TestCase::CreateTestIsolate(); | 
|  | EXPECT(new_isolate != nullptr); | 
|  | Dart_SetMessageNotifyCallback(&NotifyMessage); | 
|  | Dart_EnterScope(); | 
|  | // Reload all the test classes here. | 
|  | // | 
|  | // TODO(turnidge): Use the create isolate callback instead? | 
|  | Dart_Handle lib = | 
|  | TestCase::LoadTestScript(kCustomIsolateScriptChars, NativeLookup); | 
|  | EXPECT_VALID(lib); | 
|  |  | 
|  | Dart_Handle main_send_port = Dart_GetField(lib, NewString("mainSendPort")); | 
|  | EXPECT_VALID(main_send_port); | 
|  | Dart_Port main_port_id; | 
|  | Dart_Handle err = Dart_SendPortGetId(main_send_port, &main_port_id); | 
|  | EXPECT_VALID(err); | 
|  |  | 
|  | OS::PrintErr("-- Adding StartEvent to queue --\n"); | 
|  | event_queue->Add(new StartEvent(new_isolate, isolate_main)); | 
|  |  | 
|  | // Restore the original isolate. | 
|  | Dart_ExitScope(); | 
|  | Dart_ExitIsolate(); | 
|  | Dart_EnterIsolate(saved_isolate); | 
|  | Dart_EnterScope(); | 
|  |  | 
|  | Dart_Handle send_port = Dart_NewSendPort(main_port_id); | 
|  | EXPECT_VALID(send_port); | 
|  | Dart_SetReturnValue(args, send_port); | 
|  |  | 
|  | OS::PrintErr("-- Exit: CustomIsolateImpl_start --\n"); | 
|  | Dart_ExitScope(); | 
|  | } | 
|  |  | 
|  | VM_UNIT_TEST_CASE(CustomIsolates) { | 
|  | bool saved_flag = FLAG_trace_shutdown; | 
|  | FLAG_trace_shutdown = true; | 
|  | event_queue = new EventQueue(); | 
|  |  | 
|  | Dart_Isolate dart_isolate = TestCase::CreateTestIsolate(); | 
|  | EXPECT(dart_isolate != nullptr); | 
|  | Dart_SetMessageNotifyCallback(&NotifyMessage); | 
|  | Dart_EnterScope(); | 
|  | Dart_Handle result; | 
|  |  | 
|  | // Create a test library. | 
|  | Dart_Handle lib = | 
|  | TestCase::LoadTestScript(kCustomIsolateScriptChars, NativeLookup); | 
|  | EXPECT_VALID(lib); | 
|  |  | 
|  | // Run main. | 
|  | result = Dart_Invoke(lib, NewString("main"), 0, nullptr); | 
|  | EXPECT_VALID(result); | 
|  | EXPECT(Dart_IsString(result)); | 
|  | const char* result_str = nullptr; | 
|  | EXPECT_VALID(Dart_StringToCString(result, &result_str)); | 
|  | EXPECT_STREQ("success", result_str); | 
|  |  | 
|  | Dart_ExitScope(); | 
|  | Dart_ExitIsolate(); | 
|  |  | 
|  | OS::PrintErr("-- Starting event loop --\n"); | 
|  | Event* event = event_queue->Get(); | 
|  | while (event != nullptr) { | 
|  | event->Process(); | 
|  | delete event; | 
|  | event = event_queue->Get(); | 
|  | } | 
|  | OS::PrintErr("-- Finished event loop --\n"); | 
|  | EXPECT_STREQ("Received: 43", saved_echo); | 
|  | free(saved_echo); | 
|  |  | 
|  | delete event_queue; | 
|  | event_queue = nullptr; | 
|  | FLAG_trace_shutdown = saved_flag; | 
|  | } | 
|  |  | 
|  | }  // namespace dart |