[wasm] [debugger] First version of multithreaded debugging (#74820)
authorThays Grazia <thaystg@gmail.com>
Wed, 1 Feb 2023 23:20:33 +0000 (20:20 -0300)
committerGitHub <noreply@github.com>
Wed, 1 Feb 2023 23:20:33 +0000 (20:20 -0300)
* First version of multithreaded debugging.

* Revert package-lock.json

* New line at package-lock.json

* Fix not used variable.

* Fix debugger on firefox.

* Rewrite code to avoid duplicated code.

* Fix where mono_init_debugger_agent_common is called.

* Remove whitespace.

* Update src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs

Co-authored-by: Ankit Jain <radical@gmail.com>
* Update src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs

Co-authored-by: Ankit Jain <radical@gmail.com>
* Update src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs

Co-authored-by: Ankit Jain <radical@gmail.com>
* [wasm] Debugger tests: support running with multithreaded runtime

* Add runtime-wasm-dbgtests pipeline with debugger tests running on a multi-threaded runtime

* Add multi-threaded debugger tests to runtime-wasm

* fix yml

* Always run the new tests when the pipeline is invoked manually

* Pass through extra build args for wasm debugger tests

* Addressing @radical comments.

* Apply suggestions from code review

Co-authored-by: Ankit Jain <radical@gmail.com>
* addressing radical comments

* Fixing tests failures and adding a schema to run a test that will only run in a multithreaded environment.

* Adding support for run debugger-tests in a multithreaded runtime.

* Fix running debugger tests for multithreaded runtime, passing sessionId where it's necessary.

* Fix CI.

* Addressing @radical comments
Adding a test case.

* Update src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs

Co-authored-by: Ankit Jain <radical@gmail.com>
* Update src/mono/wasm/debugger/DebuggerTestSuite/MiscTests.cs

Co-authored-by: Ankit Jain <radical@gmail.com>
* Update src/mono/wasm/debugger/DebuggerTestSuite/InspectorClient.cs

Co-authored-by: Ankit Jain <radical@gmail.com>
* Dictionary with the scriptId also uses sessionId.

* Addressing @radical review.

* Apply suggestions from code review

Co-authored-by: Ankit Jain <radical@gmail.com>
* Avoiding getting this error: Cannot transition thread 0x2a15360 from STATE_BLOCKING with DO_BLOCKING.
In the transport_send we don't save the thread context, we save it before the send function.

* Addressing @radical comments.

* Using more threads in unit test.

* Apply suggestions from code review

Co-authored-by: Ankit Jain <radical@gmail.com>
* Addressing @radical comments, and trying to fix ci.

* Removing unnecessary changes.

* Export function used on mini-wasm-debugger.

* Fixing line number.

* Fix run tests on release.

* fix compilation for multithread runtime

* trying to fix multithread debugger tests on ci

* trying to fix debugger tests on ci

* disabling tests on multithreaded runtime

* Update eng/pipelines/extra-platforms/runtime-extra-platforms-wasm.yml

Co-authored-by: Ankit Jain <radical@gmail.com>
* Throwing an exception if the "what" is not the one that is being get from the nextNotificationQueue.

---------

Co-authored-by: Ankit Jain <radical@gmail.com>
31 files changed:
eng/pipelines/common/templates/wasm-debugger-tests.yml
eng/pipelines/extra-platforms/runtime-extra-platforms-wasm.yml
eng/pipelines/runtime-wasm-dbgtests.yml [new file with mode: 0644]
src/mono/mono/component/debugger-agent.c
src/mono/mono/component/mini-wasm-debugger.c
src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs
src/mono/wasm/debugger/BrowserDebugProxy/Firefox/FirefoxMonoProxy.cs
src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs
src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs
src/mono/wasm/debugger/DebuggerTestSuite/AsyncTests.cs
src/mono/wasm/debugger/DebuggerTestSuite/BreakpointTests.cs
src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs
src/mono/wasm/debugger/DebuggerTestSuite/EnvironmentVariables.cs
src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests2.cs
src/mono/wasm/debugger/DebuggerTestSuite/FirefoxInspectorClient.cs
src/mono/wasm/debugger/DebuggerTestSuite/Inspector.cs
src/mono/wasm/debugger/DebuggerTestSuite/InspectorClient.cs
src/mono/wasm/debugger/DebuggerTestSuite/MiscTests.cs
src/mono/wasm/debugger/DebuggerTestSuite/SteppingTests.cs
src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessOptions.cs
src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessProxy.cs
src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessStartup.cs
src/mono/wasm/debugger/Wasm.Debugger.Tests/Wasm.Debugger.Tests.csproj
src/mono/wasm/debugger/tests/debugger-test/debugger-test.cs
src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj
src/mono/wasm/host/WebServerStartup.cs
src/mono/wasm/runtime/debug.ts
src/mono/wasm/runtime/dotnet-legacy.d.ts
src/mono/wasm/runtime/es6/dotnet.es6.lib.js
src/mono/wasm/runtime/exports-linker.ts
src/mono/wasm/runtime/rollup.config.js

index 94fd9e0..ff3b33f 100644 (file)
@@ -4,6 +4,8 @@ parameters:
   isWasmOnlyBuild: false
   browser: 'chrome'
   shouldContinueOnError: false
+  extraBuildArgs: ''
+  nameSuffix: ''
   platforms: []
 
 jobs:
@@ -30,8 +32,11 @@ jobs:
     jobParameters:
       testGroup: innerloop
       isExtraPlatforms: ${{ parameters.isExtraPlatformsBuild }}
-      nameSuffix: Mono_DebuggerTests_${{ parameters.browser }}
-      buildArgs: -s mono+libs+libs.tests -c $(_BuildConfig) /p:ArchiveTests=true /p:TestWasmDebuggerTests=true /p:TestAssemblies=false /p:BrowserHost=$(_hostedOs) /p:DebuggerHost=${{ parameters.browser }}
+      ${{ if eq(parameters.nameSuffix, '') }}:
+        nameSuffix: Mono_DebuggerTests_${{ parameters.browser }}
+      ${{ else }}:
+        nameSuffix: ${{ parameters.nameSuffix }}
+      buildArgs: -s mono+libs+libs.tests -c $(_BuildConfig) /p:ArchiveTests=true /p:TestWasmDebuggerTests=true /p:TestAssemblies=false /p:BrowserHost=$(_hostedOs) /p:DebuggerHost=${{ parameters.browser }} ${{ parameters.extraBuildArgs }}
       timeoutInMinutes: 180
       # if !alwaysRun, then:
       #   if this is runtime-wasm (isWasmOnlyBuild):
index 28ed661..a5b6e05 100644 (file)
@@ -99,7 +99,7 @@ jobs:
         - WasmTestOnBrowser
         - WasmTestOnNodeJS
 
-  # Library tests with full threading 
+  # Library tests with full threading
   - template: /eng/pipelines/common/templates/wasm-library-tests.yml
     parameters:
       platforms:
@@ -212,6 +212,17 @@ jobs:
       # ff tests are unstable currently
       shouldContinueOnError: true
 
+  - template: /eng/pipelines/common/templates/wasm-debugger-tests.yml
+    parameters:
+      platforms:
+        - Browser_wasm
+        - Browser_wasm_win
+      extraBuildArgs: /p:MonoWasmBuildVariant=multithread /p:WasmEnableThreads=true
+      nameSuffix: DebuggerTests_MultiThreaded
+      alwaysRun: ${{ parameters.isWasmOnlyBuild }}
+      isExtraPlatformsBuild: ${{ parameters.isExtraPlatformsBuild }}
+      isWasmOnlyBuild: ${{ parameters.isWasmOnlyBuild }}
+
   # Disable for now
   #- template: /eng/pipelines/coreclr/perf-wasm-jobs.yml
     #parameters:
diff --git a/eng/pipelines/runtime-wasm-dbgtests.yml b/eng/pipelines/runtime-wasm-dbgtests.yml
new file mode 100644 (file)
index 0000000..85e6669
--- /dev/null
@@ -0,0 +1,41 @@
+trigger: none
+
+variables:
+  - template: /eng/pipelines/common/variables.yml
+
+jobs:
+
+#
+# Evaluate paths
+#
+- template: /eng/pipelines/common/evaluate-default-paths.yml
+
+# Debugger tests
+- template: /eng/pipelines/common/templates/wasm-debugger-tests.yml
+  parameters:
+    platforms:
+      - Browser_wasm
+      - Browser_wasm_win
+    isExtraPlatformsBuild: ${{ parameters.isExtraPlatformsBuild }}
+    isWasmOnlyBuild: ${{ parameters.isWasmOnlyBuild }}
+
+- template: /eng/pipelines/common/templates/wasm-debugger-tests.yml
+  parameters:
+    platforms:
+      - Browser_wasm
+      - Browser_wasm_win
+    extraBuildArgs: /p:MonoWasmBuildVariant=multithread /p:WasmEnableThreads=true
+    nameSuffix: DebuggerTests_MultiThreaded
+    isExtraPlatformsBuild: ${{ parameters.isExtraPlatformsBuild }}
+    isWasmOnlyBuild: ${{ parameters.isWasmOnlyBuild }}
+
+- template: /eng/pipelines/common/templates/wasm-debugger-tests.yml
+  parameters:
+    platforms:
+      - Browser_wasm_firefox
+    browser: firefox
+    isExtraPlatformsBuild: ${{ parameters.isExtraPlatformsBuild }}
+    isWasmOnlyBuild: ${{ parameters.isWasmOnlyBuild }}
+    alwaysRun: ${{ parameters.isWasmOnlyBuild }}
+    # ff tests are unstable currently
+    shouldContinueOnError: true
index 6956c42..8e13638 100644 (file)
@@ -323,9 +323,6 @@ typedef struct {
 /*
  * Globals
  */
-#ifdef TARGET_WASM
-static DebuggerTlsData debugger_wasm_thread;
-#endif
 static AgentConfig agent_config;
 
 /*
@@ -414,7 +411,7 @@ static gint32 suspend_count;
 /* Whenever to buffer reply messages and send them together */
 static gboolean buffer_replies;
 
-#ifndef TARGET_WASM
+
 #define GET_TLS_DATA_FROM_THREAD(thread) \
        DebuggerTlsData *tls = NULL; \
        mono_loader_lock(); \
@@ -424,15 +421,6 @@ static gboolean buffer_replies;
 #define GET_DEBUGGER_TLS() \
        DebuggerTlsData *tls; \
        tls = (DebuggerTlsData *)mono_native_tls_get_value (debugger_tls_id);
-#else
-/* the thread argument is omitted on wasm, to avoid compiler warning */
-#define GET_TLS_DATA_FROM_THREAD(...) \
-       DebuggerTlsData *tls; \
-       tls = &debugger_wasm_thread;
-#define GET_DEBUGGER_TLS() \
-       DebuggerTlsData *tls; \
-       tls = &debugger_wasm_thread;
-#endif
 
 #define GET_EXTRA_SPACE_FOR_REF_FIELDS(klass) \
        extra_space_size = 0; \
@@ -528,6 +516,8 @@ static void process_profiler_event (EventKind event, gpointer arg);
 
 static void invalidate_frames (DebuggerTlsData *tls);
 
+static void mono_init_debugger_agent_common (MonoProfilerHandle *prof);
+
 /* Callbacks used by debugger-engine */
 static MonoContext* tls_get_restore_state (void *the_tls);
 static gboolean try_process_suspend (void *tls, MonoContext *ctx, gboolean from_breakpoint);
@@ -814,25 +804,13 @@ mono_debugger_agent_init_internal (void)
        mono_profiler_set_domain_loaded_callback (prof, appdomain_load);
        mono_profiler_set_domain_unloading_callback (prof, appdomain_start_unload);
        mono_profiler_set_domain_unloaded_callback (prof, appdomain_unload);
-       mono_profiler_set_thread_started_callback (prof, thread_startup);
-       mono_profiler_set_thread_stopped_callback (prof, thread_end);
        mono_profiler_set_assembly_loaded_callback (prof, assembly_load);
        mono_profiler_set_assembly_unloading_callback (prof, assembly_unload);
-       mono_profiler_set_jit_done_callback (prof, jit_done);
        mono_profiler_set_jit_failed_callback (prof, jit_failed);
        mono_profiler_set_gc_finalizing_callback (prof, gc_finalizing);
        mono_profiler_set_gc_finalized_callback (prof, gc_finalized);
 
-       mono_native_tls_alloc (&debugger_tls_id, NULL);
-
-       /* Needed by the hash_table_new_type () call below */
-       mono_gc_base_init ();
-
-       thread_to_tls = mono_g_hash_table_new_type_internal ((GHashFunc)mono_object_hash_internal, NULL, MONO_HASH_KEY_GC, MONO_ROOT_SOURCE_DEBUGGER, NULL, "Debugger TLS Table");
-
-       tid_to_thread = mono_g_hash_table_new_type_internal (NULL, NULL, MONO_HASH_VALUE_GC, MONO_ROOT_SOURCE_DEBUGGER, NULL, "Debugger Thread Table");
-
-       tid_to_thread_obj = mono_g_hash_table_new_type_internal (NULL, NULL, MONO_HASH_VALUE_GC, MONO_ROOT_SOURCE_DEBUGGER, NULL, "Debugger Thread Object Table");
+       mono_init_debugger_agent_common (&prof);
 
        pending_assembly_loads = g_ptr_array_new ();
 
@@ -852,7 +830,6 @@ mono_debugger_agent_init_internal (void)
        }
        mono_de_set_log_level (log_level, log_file);
 
-       ids_init ();
        objrefs_init ();
        suspend_init ();
 
@@ -1636,6 +1613,31 @@ static GHashTable *obj_to_objref;
 /* Protected by the dbg lock */
 static MonoGHashTable *suspended_objs;
 
+static void
+mono_init_debugger_agent_common (MonoProfilerHandle *prof)
+{
+       ids_init ();
+
+       event_requests = g_ptr_array_new ();
+
+       pending_assembly_loads = g_ptr_array_new ();
+
+       mono_profiler_set_thread_started_callback (*prof, thread_startup);
+       mono_profiler_set_thread_stopped_callback (*prof, thread_end);
+       mono_profiler_set_jit_done_callback (*prof, jit_done);
+       
+       mono_native_tls_alloc (&debugger_tls_id, NULL);
+
+       /* Needed by the hash_table_new_type () call below */
+       mono_gc_base_init ();
+
+       thread_to_tls = mono_g_hash_table_new_type_internal ((GHashFunc)mono_object_hash_internal, NULL, MONO_HASH_KEY_GC, MONO_ROOT_SOURCE_DEBUGGER, NULL, "Debugger TLS Table");
+
+       tid_to_thread = mono_g_hash_table_new_type_internal (NULL, NULL, MONO_HASH_VALUE_GC, MONO_ROOT_SOURCE_DEBUGGER, NULL, "Debugger Thread Table");
+
+       tid_to_thread_obj = mono_g_hash_table_new_type_internal (NULL, NULL, MONO_HASH_VALUE_GC, MONO_ROOT_SOURCE_DEBUGGER, NULL, "Debugger Thread Object Table");
+}
+
 #ifdef TARGET_WASM
 void
 mono_init_debugger_agent_for_wasm (int log_level_parm, MonoProfilerHandle *prof)
@@ -1646,23 +1648,17 @@ mono_init_debugger_agent_for_wasm (int log_level_parm, MonoProfilerHandle *prof)
        int ntransports = 0;
        DebuggerTransport *transports = mono_debugger_agent_get_transports (&ntransports);
 
-       ids_init();
        objrefs = g_hash_table_new_full (NULL, NULL, NULL, mono_debugger_free_objref);
        obj_to_objref = g_hash_table_new (NULL, NULL);
-       pending_assembly_loads = g_ptr_array_new ();
 
        log_level = log_level_parm;
-       event_requests = g_ptr_array_new ();
+
        vm_start_event_sent = TRUE;
        transport = &transports [0];
 
-       memset(&debugger_wasm_thread, 0, sizeof(DebuggerTlsData));
-       mono_native_tls_alloc (&debugger_tls_id, NULL);
-       mono_native_tls_set_value (debugger_tls_id, &debugger_wasm_thread);
-
        agent_config.enabled = TRUE;
 
-       mono_profiler_set_jit_done_callback (*prof, jit_done);
+       mono_init_debugger_agent_common (prof);
 }
 
 void
@@ -2255,14 +2251,17 @@ save_thread_context (MonoContext *ctx)
 void
 mono_wasm_save_thread_context (void)
 {
-       debugger_wasm_thread.really_suspended = TRUE;
-       mono_thread_state_init_from_current (&debugger_wasm_thread.context);
+       DebuggerTlsData* tls = mono_wasm_get_tls ();
+       tls->really_suspended = TRUE;
+       mono_thread_state_init_from_current (&tls->context);
 }
 
 DebuggerTlsData*
 mono_wasm_get_tls (void)
 {
-       return &debugger_wasm_thread;
+       MonoThread *thread = mono_thread_current ();
+       GET_TLS_DATA_FROM_THREAD (thread);
+       return tls;
 }
 #endif
 
@@ -2855,7 +2854,11 @@ wait_for_suspend (void)
 static gboolean
 is_suspended (void)
 {
+#ifdef HOST_WASM
+       return true;
+#else
        return count_threads_to_wait_for () == 0;
+#endif
 }
 
 static void
@@ -3762,6 +3765,7 @@ process_event (EventKind event, gpointer arg, gint32 il_offset, MonoContext *ctx
 
 #ifdef TARGET_WASM
        PRINT_DEBUG_MSG (1, "[%p] Sent %d events %s(%d), suspend=%d.\n", (gpointer) (gsize) mono_native_thread_id_get (), nevents, event_to_string (event), ecount, suspend_policy);
+       mono_wasm_save_thread_context();
 #endif
 
        send_success = send_packet (CMD_SET_EVENT, CMD_COMPOSITE, &buf);
@@ -3910,8 +3914,9 @@ thread_startup (MonoProfiler *prof, uintptr_t tid)
        /*
         * suspend_vm () could have missed this thread, so wait for a resume.
         */
-
+#ifndef HOST_WASM
        suspend_current_func ();
+#endif
 }
 
 static void
@@ -9313,7 +9318,7 @@ thread_commands (int command, guint8 *p, guint8 *end, Buffer *buf)
 
                // Wait for suspending if it already started
                // FIXME: Races with suspend_count
-#ifndef HOST_WASI      
+#if !defined(HOST_WASI) && !defined(HOST_WASM)
                while (!is_suspended ()) {
                        if (suspend_count)
                                wait_for_suspend ();
@@ -9498,9 +9503,7 @@ frame_commands (int command, guint8 *p, guint8 *end, Buffer *buf)
        int objid;
        ErrorCode err;
        MonoThread *thread_obj;
-#ifndef TARGET_WASM
        MonoInternalThread *thread;
-#endif
        int pos, i, len, frame_idx;
        StackFrame *frame;
        MonoDebugMethodJitInfo *jit;
@@ -9514,16 +9517,10 @@ frame_commands (int command, guint8 *p, guint8 *end, Buffer *buf)
        if (err != ERR_NONE)
                return err;
 
-#ifndef TARGET_WASM
        thread = THREAD_TO_INTERNAL (thread_obj);
-#endif
        id = decode_id (p, &p, end);
 
-#ifndef TARGET_WASM
        GET_TLS_DATA_FROM_THREAD (thread);
-#else
-       GET_TLS_DATA_FROM_THREAD ();
-#endif
        g_assert (tls);
 
        for (i = 0; i < tls->frame_count; ++i) {
index 98da53a..f9071ea 100644 (file)
@@ -41,7 +41,7 @@ EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_send_dbg_command_with_parms (int id, Mdb
 
 
 //JS functions imported that we use
-extern void mono_wasm_fire_debugger_agent_message (void);
+extern void mono_wasm_fire_debugger_agent_message_with_data (const char *data, int len);
 extern void mono_wasm_asm_loaded (const char *asm_name, const char *assembly_data, guint32 assembly_len, const char *pdb_data, guint32 pdb_len);
 
 G_END_DECLS
@@ -382,7 +382,7 @@ mono_wasm_send_dbg_command_with_parms (int id, MdbgProtCommandSet command_set, i
                goto done;
        }
        MdbgProtBuffer bufWithParms;
-       buffer_init (&bufWithParms, 128);
+       m_dbgprot_buffer_init (&bufWithParms, 128);
        m_dbgprot_buffer_add_data (&bufWithParms, data, size);
        if (!write_value_to_buffer(&bufWithParms, valtype, newvalue)) {
                mono_wasm_add_dbg_command_received(0, id, 0, 0);
@@ -410,11 +410,11 @@ mono_wasm_send_dbg_command (int id, MdbgProtCommandSet command_set, int command,
        }
        ss_calculate_framecount (NULL, NULL, TRUE, NULL, NULL);
        MdbgProtBuffer buf;
-       buffer_init (&buf, 128);
        gboolean no_reply;
        MdbgProtErrorCode error = 0;
        if (command_set == MDBGPROT_CMD_SET_VM && command == MDBGPROT_CMD_VM_INVOKE_METHOD )
        {
+               m_dbgprot_buffer_init (&buf, 128);
                DebuggerTlsData* tls = mono_wasm_get_tls ();
                InvokeData invoke_data;
                memset (&invoke_data, 0, sizeof (InvokeData));
@@ -426,6 +426,7 @@ mono_wasm_send_dbg_command (int id, MdbgProtCommandSet command_set, int command,
                char* assembly_name = m_dbgprot_decode_string (data, &data, data + size);
                if (assembly_name == NULL)
                {
+                       m_dbgprot_buffer_init (&buf, 128);
                        m_dbgprot_buffer_add_int (&buf, 0);
                        m_dbgprot_buffer_add_int (&buf, 0);
                }
@@ -435,16 +436,20 @@ mono_wasm_send_dbg_command (int id, MdbgProtCommandSet command_set, int command,
                        int symfile_size = 0;
                        const unsigned char* assembly_bytes = mono_wasm_get_assembly_bytes (assembly_name, &assembly_size);
                        const unsigned char* pdb_bytes = mono_get_symfile_bytes_from_bundle (assembly_name, &symfile_size);
+                       m_dbgprot_buffer_init (&buf, assembly_size + symfile_size);
                        m_dbgprot_buffer_add_byte_array (&buf, (uint8_t *) assembly_bytes, assembly_size);
                        m_dbgprot_buffer_add_byte_array (&buf, (uint8_t *) pdb_bytes, symfile_size);
                }
        }
        else
+       {
+               m_dbgprot_buffer_init (&buf, 128);
                error = mono_process_dbg_packet (id, command_set, command, &no_reply, data, data + size, &buf);
+       }
 
        mono_wasm_add_dbg_command_received (error == MDBGPROT_ERR_NONE, id, buf.buf, buf.p-buf.buf);
 
-       buffer_free (&buf);
+       m_dbgprot_buffer_free (&buf);
        result = TRUE;
 done:
        MONO_EXIT_GC_UNSAFE;
@@ -454,9 +459,7 @@ done:
 static gboolean
 receive_debugger_agent_message (void *data, int len)
 {
-       mono_wasm_add_dbg_command_received(1, 0, data, len);
-       mono_wasm_save_thread_context();
-       mono_wasm_fire_debugger_agent_message ();
+       mono_wasm_fire_debugger_agent_message_with_data ((const char*)data, len);
        return FALSE;
 }
 
index 731001b..2ac407e 100644 (file)
@@ -405,7 +405,22 @@ namespace Microsoft.WebAssembly.Diagnostics
             SdbAgent = sdbAgent;
             PauseOnExceptions = pauseOnExceptions;
         }
-
+        public ExecutionContext CreateChildAsyncExecutionContext(SessionId sessionId)
+            => new ExecutionContext(null, Id, AuxData, PauseOnExceptions)
+            {
+                ParentContext = this,
+                SessionId = sessionId
+            };
+        public bool CopyDataFromParentContext()
+        {
+            if (SdbAgent != null)
+                return false;
+            ready = ParentContext.ready;
+            store = ParentContext.store;
+            Source = ParentContext.Source;
+            SdbAgent = ParentContext.SdbAgent.Clone(SessionId);
+            return true;
+        }
         public string DebugId { get; set; }
         public Dictionary<string, BreakpointRequest> BreakpointRequests { get; } = new Dictionary<string, BreakpointRequest>();
         public int breakpointId;
@@ -416,6 +431,8 @@ namespace Microsoft.WebAssembly.Diagnostics
         public bool IsResumedAfterBp { get; set; }
         public int ThreadId { get; set; }
         public int Id { get; set; }
+        public ExecutionContext ParentContext { get; private set; }
+        public SessionId SessionId { get; private set; }
 
         public bool PausedOnWasm { get; set; }
 
@@ -431,8 +448,8 @@ namespace Microsoft.WebAssembly.Diagnostics
 
         public string[] LoadedFiles { get; set; }
         internal DebugStore store;
-        internal MonoSDBHelper SdbAgent { get; init; }
-        public TaskCompletionSource<DebugStore> Source { get; } = new TaskCompletionSource<DebugStore>();
+        internal MonoSDBHelper SdbAgent { get; private set; }
+        public TaskCompletionSource<DebugStore> Source { get; private set; } = new TaskCompletionSource<DebugStore>();
 
         private Dictionary<int, PerScopeCache> perScopeCaches { get; } = new Dictionary<int, PerScopeCache>();
 
index 267e9bc..41a1ea5 100644 (file)
@@ -254,11 +254,11 @@ internal sealed class FirefoxMonoProxy : MonoProxy
                     var topFunc = args["frame"]["displayName"].Value<string>();
                     switch (topFunc)
                     {
-                        case "mono_wasm_fire_debugger_agent_message":
-                        case "_mono_wasm_fire_debugger_agent_message":
+                        case "mono_wasm_fire_debugger_agent_message_with_data_to_pause":
+                        case "_mono_wasm_fire_debugger_agent_message_with_data_to_pause":
                             {
                                 ctx.PausedOnWasm = true;
-                                return await OnReceiveDebuggerAgentEvent(sessionId, args, token);
+                                return await OnReceiveDebuggerAgentEvent(sessionId, args, GetLastDebuggerAgentBuffer(args), token);
                             }
                         default:
                             ctx.PausedOnWasm = false;
@@ -694,15 +694,28 @@ internal sealed class FirefoxMonoProxy : MonoProxy
 
         context.LastDebuggerAgentBufferReceived = debuggerAgentBufferTask;
     }
+    internal static Result GetLastDebuggerAgentBuffer(JObject args)
+    {
+        var result = new JArray();
+        result.Add(JObject.FromObject(new { value = new {value = args?["frame"]?["arguments"]?[0].Value<string>()}}));
+        Result res = Result.OkFromObject(new
+                    {
+                        result
+                    });
+        return res;
+    }
 
     private async Task<bool> SendPauseToBrowser(SessionId sessionId, JObject args, CancellationToken token)
     {
         var context = GetContextFixefox(sessionId);
         Result res = await context.LastDebuggerAgentBufferReceived;
-        if (!res.IsOk)
+        if (!res.IsOk || res.Value?["result"].Value<JArray>().Count == 0)
+        {
+            logger.LogTrace($"Unexpected DebuggerAgentBufferReceived {res}");
             return false;
+        }
         context.LastDebuggerAgentBufferReceived = null;
-        byte[] newBytes = Convert.FromBase64String(res.Value?["result"]?["value"]?["value"]?.Value<string>());
+        byte[] newBytes = Convert.FromBase64String(res.Value?["result"]?[0]?["value"]?["value"]?.Value<string>());
         using var retDebuggerCmdReader = new MonoBinaryReader(newBytes);
         retDebuggerCmdReader.ReadBytes(11);
         retDebuggerCmdReader.ReadByte();
@@ -817,7 +830,7 @@ internal sealed class FirefoxMonoProxy : MonoProxy
         return SendCommand(id, "evaluateJSAsync", o, token);
     }
 
-    internal override async Task OnSourceFileAdded(SessionId sessionId, SourceFile source, ExecutionContext context, CancellationToken token)
+    internal override async Task OnSourceFileAdded(SessionId sessionId, SourceFile source, ExecutionContext context, CancellationToken token, bool resolveBreakpoints = true)
     {
         //different behavior when debugging from VSCode and from Firefox
         var ctx = context as FirefoxExecutionContext;
@@ -852,7 +865,8 @@ internal sealed class FirefoxMonoProxy : MonoProxy
             });
         }
         await SendEvent(sessionId, "", sourcesJObj, token);
-
+        if (!resolveBreakpoints)
+            return;
         foreach (var req in context.BreakpointRequests.Values)
         {
             if (req.TryResolve(source))
@@ -970,8 +984,8 @@ internal sealed class FirefoxMonoProxy : MonoProxy
             string function_name = frame["displayName"]?.Value<string>();
             if (function_name != null && !(function_name.StartsWith("Module._mono_wasm", StringComparison.Ordinal) ||
                     function_name.StartsWith("Module.mono_wasm", StringComparison.Ordinal) ||
-                    function_name == "mono_wasm_fire_debugger_agent_message" ||
-                    function_name == "_mono_wasm_fire_debugger_agent_message" ||
+                    function_name == "mono_wasm_fire_debugger_agent_message_with_data" ||
+                    function_name == "_mono_wasm_fire_debugger_agent_message_with_data" ||
                     function_name == "(wasmcall)"))
             {
                 callFrames.Add(frame);
index d680309..2ebf69b 100644 (file)
@@ -13,6 +13,8 @@ using Newtonsoft.Json;
 using Newtonsoft.Json.Linq;
 using System.Net.Http;
 using BrowserDebugProxy;
+using static System.Formats.Asn1.AsnWriter;
+using System.Reflection;
 
 namespace Microsoft.WebAssembly.Diagnostics
 {
@@ -152,9 +154,19 @@ namespace Microsoft.WebAssembly.Diagnostics
 
                 case "Debugger.paused":
                     {
-                        // Don't process events from sessions we aren't tracking
-                        if (!contexts.ContainsKey(sessionId))
-                            return false;
+                        if (args["asyncStackTraceId"] != null)
+                        {
+                            if (!contexts.TryGetValue(sessionId, out ExecutionContext context))
+                                return false;
+                            if (context.CopyDataFromParentContext())
+                            {
+                                var store = await LoadStore(sessionId, true, token);
+                                foreach (var source in store.AllSources())
+                                {
+                                    await OnSourceFileAdded(sessionId, source, context, token, false);
+                                }
+                            }
+                        }
 
                         //TODO figure out how to stich out more frames and, in particular what happens when real wasm is on the stack
                         string top_func = args?["callFrames"]?[0]?["functionName"]?.Value<string>();
@@ -175,17 +187,16 @@ namespace Microsoft.WebAssembly.Diagnostics
                                         await ReloadSymbolsFromSymbolServer(sessionId, GetContext(sessionId), token);
                                     return true;
                                 }
-                            case "mono_wasm_fire_debugger_agent_message":
-                            case "_mono_wasm_fire_debugger_agent_message":
+                            case "mono_wasm_fire_debugger_agent_message_with_data_to_pause":
+                            case "_mono_wasm_fire_debugger_agent_message_with_data_to_pause":
+                                try
                                 {
-                                    try {
-                                        return await OnReceiveDebuggerAgentEvent(sessionId, args, token);
-                                    }
-                                    catch (Exception) //if the page is refreshed maybe it stops here.
-                                    {
-                                        await SendResume(sessionId, token);
-                                        return true;
-                                    }
+                                    return await OnReceiveDebuggerAgentEvent(sessionId, args, await GetLastDebuggerAgentBuffer(sessionId, args, token), token);
+                                }
+                                catch (Exception) //if the page is refreshed maybe it stops here.
+                                {
+                                    await SendResume(sessionId, token);
+                                    return true;
                                 }
                         }
                         break;
@@ -198,8 +209,11 @@ namespace Microsoft.WebAssembly.Diagnostics
 
                 case "Target.attachedToTarget":
                     {
-                        if (args["targetInfo"]["type"]?.ToString() == "page")
+                        var targetType = args["targetInfo"]["type"]?.ToString();
+                        if (targetType == "page")
                             await AttachToTarget(new SessionId(args["sessionId"]?.ToString()), token);
+                        else if (targetType == "worker")
+                            CreateWorkerExecutionContext(new SessionId(args["sessionId"]?.ToString()), new SessionId(parms["sessionId"]?.ToString()));
                         break;
                     }
 
@@ -212,6 +226,22 @@ namespace Microsoft.WebAssembly.Diagnostics
 
             return false;
         }
+
+        protected void CreateWorkerExecutionContext(SessionId workerSessionId, SessionId originSessionId)
+        {
+            if (!contexts.TryGetValue(originSessionId, out ExecutionContext context))
+            {
+                logger.LogDebug($"Origin sessionId does not exist - {originSessionId}");
+                return;
+            }
+            if (contexts.ContainsKey(workerSessionId))
+            {
+                logger.LogDebug($"Worker sessionId already exists - {originSessionId}");
+                return;
+            }
+            contexts[workerSessionId] = context.CreateChildAsyncExecutionContext(workerSessionId);
+        }
+
         protected virtual async Task SendResume(SessionId id, CancellationToken token)
         {
             await SendCommand(id, "Debugger.resume", new JObject(), token);
@@ -1077,6 +1107,8 @@ namespace Microsoft.WebAssembly.Diagnostics
                 data,
                 hitBreakpoints = bp_list,
             });
+            if (args["asyncStackTraceId"] != null)
+                o["asyncStackTraceId"] = args["asyncStackTraceId"];
             if (!await EvaluateCondition(sessionId, context, context.CallStack.First(), bp, token))
             {
                 context.ClearState();
@@ -1092,16 +1124,29 @@ namespace Microsoft.WebAssembly.Diagnostics
         {
         }
 
-        internal async Task<bool> OnReceiveDebuggerAgentEvent(SessionId sessionId, JObject args, CancellationToken token)
+        internal async Task<Result> GetLastDebuggerAgentBuffer(SessionId sessionId, JObject args, CancellationToken token)
+        {
+            if (args?["callFrames"].Value<JArray>().Count == 0 || args["callFrames"][0]["scopeChain"].Value<JArray>().Count == 0)
+                return Result.Err($"Unexpected callFrames {args}");
+            var argsNew = JObject.FromObject(new
+            {
+                objectId = args["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value<string>(),
+            });
+            Result res = await SendCommand(sessionId, "Runtime.getProperties", argsNew, token);
+            return res;
+        }
+
+        internal async Task<bool> OnReceiveDebuggerAgentEvent(SessionId sessionId, JObject args, Result debuggerAgentBuffer, CancellationToken token)
         {
             var debuggerAgentBufferTask = SendMonoCommand(sessionId, MonoCommands.GetDebuggerAgentBufferReceived(RuntimeId), token);
             SaveLastDebuggerAgentBufferReceivedToContext(sessionId, debuggerAgentBufferTask);
-            var res = await debuggerAgentBufferTask;
-            if (!res.IsOk)
+            if (!debuggerAgentBuffer.IsOk || debuggerAgentBuffer.Value?["result"].Value<JArray>().Count == 0)
+            {
+                logger.LogTrace($"Unexpected DebuggerAgentBufferReceived {debuggerAgentBuffer}");
                 return false;
-
+            }
             ExecutionContext context = GetContext(sessionId);
-            byte[] newBytes = Convert.FromBase64String(res.Value?["result"]?["value"]?["value"]?.Value<string>());
+            byte[] newBytes = Convert.FromBase64String(debuggerAgentBuffer.Value?["result"]?[0]?["value"]?["value"]?.Value<string>());
             using var retDebuggerCmdReader = new MonoBinaryReader(newBytes);
             retDebuggerCmdReader.ReadBytes(11); //skip HEADER_LEN
             retDebuggerCmdReader.ReadByte(); //suspend_policy
@@ -1157,6 +1202,10 @@ namespace Microsoft.WebAssembly.Diagnostics
                         else if (event_kind == EventKind.Breakpoint)
                             context.PauseKind = "breakpoint";
                         Breakpoint bp = context.BreakpointRequests.Values.SelectMany(v => v.Locations).FirstOrDefault(b => b.RemoteId == request_id);
+                        if (bp == null && context.ParentContext != null)
+                        {
+                            bp = context.ParentContext.BreakpointRequests.Values.SelectMany(v => v.Locations).FirstOrDefault(b => b.RemoteId == request_id);
+                        }
                         if (request_id == context.TempBreakpointForSetNextIP)
                         {
                             context.TempBreakpointForSetNextIP = -1;
@@ -1458,12 +1507,13 @@ namespace Microsoft.WebAssembly.Diagnostics
             return bp;
         }
 
-        internal virtual async Task OnSourceFileAdded(SessionId sessionId, SourceFile source, ExecutionContext context, CancellationToken token)
+        internal virtual async Task OnSourceFileAdded(SessionId sessionId, SourceFile source, ExecutionContext context, CancellationToken token, bool resolveBreakpoints = true)
         {
             JObject scriptSource = JObject.FromObject(source.ToScriptSource(context.Id, context.AuxData));
             // Log("debug", $"sending {source.Url} {context.Id} {sessionId.sessionId}");
             await SendEvent(sessionId, "Debugger.scriptParsed", scriptSource, token);
-
+            if (!resolveBreakpoints)
+                return;
             foreach (var req in context.BreakpointRequests.Values)
             {
                 if (req.TryResolve(source))
index 6861eef..e2f6d2d 100644 (file)
@@ -839,6 +839,14 @@ namespace Microsoft.WebAssembly.Diagnostics
             ResetStore(null);
         }
 
+        public MonoSDBHelper Clone(SessionId sessionId)
+            => new MonoSDBHelper(proxy, logger, sessionId)
+            {
+                VmMajorVersion = VmMajorVersion,
+                VmMinorVersion = VmMinorVersion,
+                store = store,
+            };
+
         public void ResetStore(DebugStore store)
         {
             this.store = store;
index 96dc8f2..ec2aa8a 100644 (file)
@@ -22,7 +22,7 @@ namespace DebuggerTests
         // FIXME: check object properties..
 
         //FIXME: function name
-        [ConditionalTheory(nameof(RunningOnChrome))]
+        [ConditionalTheory(nameof(WasmSingleThreaded), nameof(RunningOnChrome))]
         [InlineData("ContinueWithStaticAsync", "DebuggerTests.AsyncTests.ContinueWithTests.ContinueWithStaticAsync.AnonymousMethod__3_0")]
         [InlineData("ContinueWithInstanceAsync", "DebuggerTests.AsyncTests.ContinueWithTests.ContinueWithInstanceAsync.AnonymousMethod__5_0")]
         public async Task AsyncLocalsInContinueWith(string method_name, string expected_method_name) => await CheckInspectLocalsAtBreakpointSite(
@@ -82,7 +82,7 @@ namespace DebuggerTests
                  }, "locals");
               });
 
-        [Theory]
+        [ConditionalTheory(nameof(WasmSingleThreaded), nameof(RunningOnChrome))]
         [InlineData("Run", 246, 16, 252, 16, "RunCSharpScope")]
         [InlineData("RunContinueWith", 277, 20, 283, 20, "RunContinueWithSameVariableName")]
         [InlineData("RunNestedContinueWith", 309, 24, 315, 24, "RunNestedContinueWithSameVariableName.AnonymousMethod__1")]
index 13abc58..4daa0be 100644 (file)
@@ -245,7 +245,7 @@ namespace DebuggerTests
             await SetBreakpoint("/debugger-driver.html", line_bp, column_bp, condition: condition);
             await SetBreakpoint("/debugger-driver.html", 80, 11);
 
-            await EvaluateAndCheck(
+            var pause_location = await EvaluateAndCheck(
                 "window.setTimeout(function() { conditional_breakpoint_test(5, 10, null); }, 1);",
                 "debugger-driver.html", line_expected, column_expected, "conditional_breakpoint_test");
         }
index 55d59bd..c55c2b1 100644 (file)
@@ -37,6 +37,11 @@ namespace DebuggerTests
 #else
             => WasmHost.Firefox;
 #endif
+
+        public static bool WasmMultiThreaded => EnvironmentVariables.WasmTestsUsingVariant == "multithreaded";
+
+        public static bool WasmSingleThreaded => !WasmMultiThreaded;
+
         public static bool RunningOnChrome => RunningOn == WasmHost.Chrome;
 
         public static bool RunningOnChromeAndLinux => RunningOn == WasmHost.Chrome && PlatformDetection.IsLinux;
@@ -144,15 +149,17 @@ namespace DebuggerTests
         {
             Func<InspectorClient, CancellationToken, List<(string, Task<Result>)>> fn = (client, token) =>
              {
-                 Func<string, (string, Task<Result>)> getInitCmdFn = (cmd) => (cmd, client.SendCommand(cmd, null, token));
+                 Func<string, JObject, (string, Task<Result>)> getInitCmdFn = (cmd, args) => (cmd, client.SendCommand(cmd, args, token));
                  var init_cmds = new List<(string, Task<Result>)>
                  {
-                    getInitCmdFn("Profiler.enable"),
-                    getInitCmdFn("Runtime.enable"),
-                    getInitCmdFn("Debugger.enable"),
-                    getInitCmdFn("Runtime.runIfWaitingForDebugger")
+                    getInitCmdFn("Profiler.enable", null),
+                    getInitCmdFn("Runtime.enable", null),
+                    getInitCmdFn("Debugger.enable", null),
+                    getInitCmdFn("Runtime.runIfWaitingForDebugger", null),
+                    getInitCmdFn("Debugger.setAsyncCallStackDepth", JObject.FromObject(new { maxDepth = 32 })),
+                    getInitCmdFn("Target.setAutoAttach", JObject.FromObject(new { autoAttach = true, waitForDebuggerOnStart = true, flatten = true }))
+                    //getInitCmdFn("ServiceWorker.enable", null)
                  };
-
                  return init_cmds;
              };
 
@@ -182,6 +189,7 @@ namespace DebuggerTests
         {
             var script_id = args?["scriptId"]?.Value<string>();
             var url = args["url"]?.Value<string>();
+            script_id += args["sessionId"]?.Value<string>();
             if (script_id.StartsWith("dotnet://"))
             {
                 var dbgUrl = args["dotNetUrl"]?.Value<string>();
@@ -322,7 +330,7 @@ namespace DebuggerTests
 
         internal virtual void CheckLocation(string script_loc, int line, int column, Dictionary<string, string> scripts, JToken location)
         {
-            var loc_str = $"{ scripts[location["scriptId"].Value<string>()] }" +
+            var loc_str = $"{ scripts[location["scriptId"].Value<string>()+cli.CurrentSessionId.sessionId] }" +
                 $"#{ location["lineNumber"].Value<int>() }" +
                 $"#{ location["columnNumber"].Value<int>() }";
 
index 0091ef0..5149888 100644 (file)
@@ -13,4 +13,5 @@ internal static class EnvironmentVariables
     public static readonly string? TestLogPath      = Environment.GetEnvironmentVariable("TEST_LOG_PATH");
     public static readonly bool    SkipCleanup      = Environment.GetEnvironmentVariable("SKIP_CLEANUP") == "1" ||
                                                        Environment.GetEnvironmentVariable("SKIP_CLEANUP") == "true";
+    public static readonly string? WasmTestsUsingVariant = Environment.GetEnvironmentVariable("WASM_TESTS_USING_VARIANT");
 }
index a03dced..e43a9f3 100644 (file)
@@ -205,7 +205,7 @@ namespace DebuggerTests
                 AssertEqual("Failed to resolve member access for DebuggerTests.InvalidEvaluateStaticClass.StaticProperty2", res.Error["result"]?["description"]?.Value<string>(), "wrong error message");
             });
 
-        [ConditionalFact(nameof(RunningOnChrome))]
+        [ConditionalFact(nameof(WasmSingleThreaded), nameof(RunningOnChrome))]
         public async Task AsyncLocalsInContinueWithBlock() => await CheckInspectLocalsAtBreakpointSite(
            "DebuggerTests.AsyncTests.ContinueWithTests", "ContinueWithStaticAsync", 4, "DebuggerTests.AsyncTests.ContinueWithTests.ContinueWithStaticAsync.AnonymousMethod__3_0",
            "window.setTimeout(function() { invoke_static_method('[debugger-test] DebuggerTests.AsyncTests.ContinueWithTests:RunAsync'); })",
index d0eff4c..931c4f4 100644 (file)
@@ -125,7 +125,7 @@ class FirefoxInspectorClient : InspectorClient
         if (res["type"]?.Value<string>() == "newSource")
         {
             var method = res["type"]?.Value<string>();
-            return onEvent(method, res, token);
+            return onEvent("", method, res, token);
         }
 
         if (res["type"]?.Value<string>() == "target-available-form" && res["target"] is JObject target)
@@ -192,7 +192,7 @@ class FirefoxInspectorClient : InspectorClient
                     break;
                 }
             }
-            return onEvent(method, res, token);
+            return onEvent("", method, res, token);
         }
         return null;
     }
index 6246248..b868384 100644 (file)
@@ -26,7 +26,7 @@ namespace DebuggerTests
 
         ConcurrentDictionary<string, TaskCompletionSource<JObject>> notifications = new ();
         ConcurrentDictionary<string, Func<JObject, CancellationToken, Task<ProtocolEventHandlerReturn>>> eventListeners = new ();
-
+        ConcurrentQueue<(string, JObject)> nextNotifications = new (); //in a multithreaded runtime we can receive more than one pause at same time
         public const string PAUSE = "pause";
         public const string APP_READY = "app-ready";
         public CancellationToken Token { get; }
@@ -81,12 +81,22 @@ namespace DebuggerTests
             {
                 if (tcs.Task.IsCompleted)
                 {
+                    Client.CurrentSessionId = new SessionId(tcs.Task.Result["sessionId"]?.Value<string>());
                     notifications.Remove(what, out _);
                     return tcs.Task;
                 }
 
                 throw new Exception($"Invalid internal state, waiting for {what} while another wait is already setup");
             }
+            else if (nextNotifications.TryDequeue(out (string what, JObject args) notification))
+            {
+                var n = new TaskCompletionSource<JObject>();
+                Client.CurrentSessionId = new SessionId(notification.args["sessionId"]?.Value<string>());
+                if (what != notification.what)
+                    throw new Exception($"Unexpected different notification type");
+                n.SetResult(notification.args);
+                return n.Task;
+            }
             else
             {
                 var n = new TaskCompletionSource<JObject>();
@@ -106,8 +116,12 @@ namespace DebuggerTests
             if (notifications.TryGetValue(what, out TaskCompletionSource<JObject>? tcs))
             {
                 if (tcs.Task.IsCompleted)
-                    throw new Exception($"Invalid internal state. Notifying for {what} again, but the previous one hasn't been read.");
-
+                {
+                    nextNotifications.Enqueue((what, args));
+                    return;
+                    //throw new Exception($"Invalid internal state. Notifying for {what} again, but the previous one hasn't been read.");
+                }
+                Client.CurrentSessionId = new SessionId(args["sessionId"]?.Value<string>());
                 notifications[what].SetResult(args);
                 notifications.Remove(what, out _);
             }
@@ -194,14 +208,28 @@ namespace DebuggerTests
             return ($"console.{type}: {output}", type);
         }
 
-        async Task OnMessage(string method, JObject args, CancellationToken token)
+        async Task OnMessage(string sessionId, string method, JObject args, CancellationToken token)
         {
             bool fail = false;
             switch (method)
             {
+                case "Target.attachedToTarget":
+                {
+                    var sessionIdNewTarget = new SessionId(args["sessionId"]?.Value<string>());
+                    await Client.SendCommand(sessionIdNewTarget, "Profiler.enable", null, token);
+                    await Client.SendCommand(sessionIdNewTarget, "Runtime.enable", null, token);
+                    await Client.SendCommand(sessionIdNewTarget, "Debugger.enable", null, token);
+                    await Client.SendCommand(sessionIdNewTarget, "Runtime.runIfWaitingForDebugger", null, token);
+                    await Client.SendCommand(sessionIdNewTarget, "Debugger.setAsyncCallStackDepth", JObject.FromObject(new { maxDepth = 32}), token);
+                    break;
+                }
                 case "Debugger.paused":
+                {
+                    if (sessionId != "")
+                        args.Add("sessionId", sessionId);
                     NotifyOf(PAUSE, args);
                     break;
+                }
                 case "Mono.runtimeReady":
                 {
                     _gotRuntimeReady = true;
@@ -262,6 +290,8 @@ namespace DebuggerTests
             if (eventListeners.TryGetValue(method, out Func<JObject, CancellationToken, Task<ProtocolEventHandlerReturn>>? listener)
                     && listener != null)
             {
+                if (sessionId != "")
+                    args.Add("sessionId", sessionId);
                 ProtocolEventHandlerReturn result = await listener(args, token).ConfigureAwait(false);
                 if (result is ProtocolEventHandlerReturn.RemoveHandler)
                     eventListeners.Remove(method, out _);
index ad4aab9..795face 100644 (file)
@@ -18,9 +18,11 @@ namespace DebuggerTests
     internal class InspectorClient : DevToolsClient
     {
         protected Dictionary<MessageId, TaskCompletionSource<Result>> pending_cmds = new Dictionary<MessageId, TaskCompletionSource<Result>>();
-        protected Func<string, JObject, CancellationToken, Task> onEvent;
+        protected Func<string, string, JObject, CancellationToken, Task> onEvent;
         protected int next_cmd_id;
 
+        public SessionId CurrentSessionId { get; set; } = SessionId.Null;
+
         public InspectorClient(ILogger logger) : base(logger) { }
 
         protected override async Task<WasmDebuggerConnection> SetupConnection(Uri webserverUri, CancellationToken token)
@@ -34,7 +36,7 @@ namespace DebuggerTests
             var res = JObject.Parse(msg);
 
             if (res["id"] == null)
-                return onEvent(res["method"].Value<string>(), res["params"] as JObject, token);
+                return onEvent(res["sessionId"]?.Value<string>(), res["method"].Value<string>(), res["params"] as JObject, token);
 
             var id = res.ToObject<MessageId>();
             if (!pending_cmds.Remove(id, out var item))
@@ -51,7 +53,7 @@ namespace DebuggerTests
 
         public virtual async Task Connect(
             Uri uri,
-            Func<string, JObject, CancellationToken, Task> onEvent,
+            Func<string, string, JObject, CancellationToken, Task> onEvent,
             CancellationTokenSource cts)
         {
             this.onEvent = onEvent;
@@ -75,7 +77,7 @@ namespace DebuggerTests
         }
 
         public Task<Result> SendCommand(string method, JObject args, CancellationToken token)
-            => SendCommand(new SessionId(null), method, args, token);
+            => SendCommand(CurrentSessionId, method, args, token);
 
         public virtual Task<Result> SendCommand(SessionId sessionId, string method, JObject args, CancellationToken token)
         {
@@ -90,6 +92,8 @@ namespace DebuggerTests
                 @params = args
             });
 
+            if (sessionId != SessionId.Null)
+                o.Add("sessionId", sessionId.sessionId);
             var tcs = new TaskCompletionSource<Result>();
             pending_cmds[new MessageId(sessionId.sessionId, id)] = tcs;
 
index 737ca1a..fed9b6a 100644 (file)
@@ -858,7 +858,7 @@ namespace DebuggerTests
             Assert.False(source.Value["scriptSource"].Value<string>().Contains("// Unable to read document"));
         }
 
-        [ConditionalFact(nameof(RunningOnChrome))]
+        [ConditionalFact(nameof(WasmSingleThreaded), nameof(RunningOnChrome))]
         public async Task InspectTaskAtLocals() => await CheckInspectLocalsAtBreakpointSite(
             "InspectTask",
             "RunInspectTask",
@@ -1148,5 +1148,31 @@ namespace DebuggerTests
                 }
             );
         }
+
+        [ConditionalFact(nameof(WasmMultiThreaded))]
+        public async Task TestDebugUsingMultiThreadedRuntime()
+        {
+            var bp = await SetBreakpointInMethod("debugger-test.dll", "MultiThreadedTest", "Write", 2);
+            var expression = $"{{ invoke_static_method('[debugger-test] MultiThreadedTest:Run'); }}";
+
+            var pause_location = await EvaluateAndCheck(
+                "window.setTimeout(function() {" + expression + "; }, 1);",
+                "dotnet://debugger-test.dll/debugger-test.cs", 1598, 8,
+                "MultiThreadedTest.Write");
+
+            var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+            Assert.Equal(locals[1]["value"]["type"], "number");
+            Assert.Equal(locals[1]["name"], "currentThread");
+
+            pause_location = await StepAndCheck(StepKind.Resume, "dotnet://debugger-test.dll/debugger-test.cs", 1598, 8, "MultiThreadedTest.Write");
+            locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+            Assert.Equal(locals[1]["value"]["type"], "number");
+            Assert.Equal(locals[1]["name"], "currentThread");
+
+            pause_location = await StepAndCheck(StepKind.Resume, "dotnet://debugger-test.dll/debugger-test.cs", 1598, 8, "MultiThreadedTest.Write");
+            locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+            Assert.Equal(locals[1]["value"]["type"], "number");
+            Assert.Equal(locals[1]["name"], "currentThread");
+        }
     }
 }
index 868eb1d..a3f58e4 100644 (file)
@@ -717,7 +717,7 @@ namespace DebuggerTests
             await StepAndCheck(StepKind.Out, source_file, 15, 4, "TestAsyncStepOut");
         }
 
-        [Fact]
+        [ConditionalFact(nameof(WasmSingleThreaded))]
         public async Task ResumeOutOfAsyncMethodToAsyncCallerWithBreakpoint()
         {
             string source_file = "dotnet://debugger-test.dll/debugger-async-step.cs";
index c3b9bc6..c22f056 100644 (file)
@@ -14,6 +14,8 @@ namespace DebuggerTests
         public string PagePath { get; set; }
         public string NodeApp { get; set; }
         public string BrowserParms { get; set; }
+        public bool WebServerUseCors { get; set; }
+        public bool WebServerUseCrossOriginPolicy { get; set; }
         public Func<string, ILogger<TestHarnessProxy>, Task<string>> ExtractConnUrl { get; set; }
     }
 }
index 5124e34..21cfe68 100644 (file)
@@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Hosting;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
 using Microsoft.WebAssembly.Diagnostics;
 using Xunit.Abstractions;
 
@@ -36,6 +37,15 @@ namespace DebuggerTests
 
         public static Task Start(string appPath, string pagePath, string url, ITestOutputHelper testOutput)
         {
+            TestHarnessOptions options = new()
+            {
+                AppPath = appPath,
+                PagePath = pagePath,
+                DevToolsUrl = new Uri(url),
+                WebServerUseCors = false,
+                WebServerUseCrossOriginPolicy = true
+            };
+
             lock (proxyLock)
             {
                 if (hostTask != null)
@@ -76,13 +86,17 @@ namespace DebuggerTests
                     })
                     .ConfigureServices((ctx, services) =>
                     {
-                        services.Configure<TestHarnessOptions>(ctx.Configuration);
-                        services.Configure<TestHarnessOptions>(options =>
+                        if (options.WebServerUseCors)
                         {
-                            options.AppPath = appPath;
-                            options.PagePath = pagePath;
-                            options.DevToolsUrl = new Uri(url);
-                        });
+                            services.AddCors(o => o.AddPolicy("AnyCors", builder =>
+                                {
+                                    builder.AllowAnyOrigin()
+                                        .AllowAnyMethod()
+                                        .AllowAnyHeader()
+                                        .WithExposedHeaders("*");
+                                }));
+                        }
+                        services.AddSingleton(Options.Create(options));
                     })
                     .UseStartup<TestHarnessStartup>()
                     .UseUrls(Endpoint.ToString())
index 01a3d47..168c17d 100644 (file)
@@ -75,15 +75,14 @@ namespace DebuggerTests
         }
 
         // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
-        public void Configure(IApplicationBuilder app, IOptionsMonitor<TestHarnessOptions> optionsAccessor, IWebHostEnvironment env, ILogger<TestHarnessProxy> logger, ILoggerFactory loggerFactory)
+        public void Configure(IApplicationBuilder app, IOptions<TestHarnessOptions> optionsContainer, IWebHostEnvironment env, ILogger<TestHarnessProxy> logger, ILoggerFactory loggerFactory)
         {
             this.Logger = logger;
             this._loggerFactory = loggerFactory;
+            TestHarnessOptions options = optionsContainer.Value;
 
             app.UseWebSockets();
 
-            TestHarnessOptions options = optionsAccessor.CurrentValue;
-
             var provider = new FileExtensionContentTypeProvider();
             provider.Mappings[".wasm"] = "application/wasm";
 
@@ -92,9 +91,21 @@ namespace DebuggerTests
                 FileProvider = new PhysicalFileProvider(options.AppPath),
                 ServeUnknownFileTypes = true, //Cuz .wasm is not a known file type :cry:
                 RequestPath = "",
-                ContentTypeProvider = provider
+                ContentTypeProvider = provider,
+                OnPrepareResponse = (context) => {
+                    if (options.WebServerUseCrossOriginPolicy)
+                    {
+                        context.Context.Response.Headers.Add("Cross-Origin-Embedder-Policy", "require-corp");
+                        context.Context.Response.Headers.Add("Cross-Origin-Opener-Policy", "same-origin");
+                    }
+                }
             });
 
+            if (options.WebServerUseCors)
+            {
+                app.UseCors("AnyCors");
+            }
+
             app.UseRouter(router =>
             {
                 router.MapGet("launch-host-and-connect", async context =>
index 2446869..5d23721 100644 (file)
@@ -66,6 +66,7 @@
       <_DotnetCommand Condition="'$(OS)' == 'Windows_NT'">dotnet.exe</_DotnetCommand>
 
       <RunScriptCommand>$(_DotnetCommand) test DebuggerTestSuite/DebuggerTestSuite.dll</RunScriptCommand>
+      <RunScriptCommand Condition="'$(MonoWasmBuildVariant)' == 'multithread'">$(RunScriptCommand) /e:WASM_TESTS_USING_VARIANT=multithreaded</RunScriptCommand>
       <RunScriptCommand>$(RunScriptCommand) &quot;-l:trx%3BLogFileName=testResults.trx&quot;</RunScriptCommand>
       <RunScriptCommand Condition="'$(ContinuousIntegrationBuild)' == 'true'">$(RunScriptCommand) &quot;-l:console%3BVerbosity=normal&quot;</RunScriptCommand>
 
index b7d397d..9dca7f3 100644 (file)
@@ -1394,7 +1394,7 @@ public class ReadOnlySpanTest
         public void Modify(double newDouble) {
             r1.d1 = newDouble;
         }
-        
+
         public double Run() {
             return r1.d1;
         }
@@ -1422,7 +1422,7 @@ public class ReadOnlySpanTest
         myR1.s2 = new S1();
         myR1.s2.d1 = 30;
         myR1.s2.d2 = 40;
-        double xyz = 123.0;        
+        double xyz = 123.0;
         R2Sample2 r2 = new R2Sample2(ref xyz);
         xyz = 456.0;
         System.Diagnostics.Debugger.Break();
@@ -1576,3 +1576,27 @@ public class TestLoadSymbols
         array.Add(date);
     }
 }
+
+public class MultiThreadedTest
+{
+    public static void Run()
+    {
+        System.Collections.Generic.List<System.Threading.Thread> myThreads = new();
+        for (int i = 0 ; i < 3; i++)
+        {
+            var t = new System.Threading.Thread (() => Write("y"));
+            myThreads.Add(t);
+            t.Start();
+        }
+        foreach (System.Threading.Thread curThread in myThreads)
+        {
+            curThread.Join();
+        }
+    }
+    static void Write(string input)
+    {
+        var currentThread = System.Threading.Thread.CurrentThread.ManagedThreadId;
+        Console.WriteLine($"Thread:{currentThread} - {input}");
+    }
+}
+
index 4d01709..ca76221 100644 (file)
@@ -8,6 +8,7 @@
     <WasmGenerateAppBundle>true</WasmGenerateAppBundle>
     <OutputType>library</OutputType>
     <WasmEmitSymbolMap>true</WasmEmitSymbolMap>
+    <_WasmPThreadPoolSize Condition="'$(WasmEnableThreads)'=='true'">10</_WasmPThreadPoolSize>
     <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
   </PropertyGroup>
   <ItemGroup>
index 26fe8e0..9285c83 100644 (file)
@@ -168,7 +168,6 @@ internal sealed class WebServerStartup
             });
         });
 
-
         applicationLifetime.ApplicationStarted.Register(() =>
         {
             TaskCompletionSource<ServerURLs> tcs = realUrlsAvailableTcs;
index 66b8cec..bb856bf 100644 (file)
@@ -29,14 +29,21 @@ export function mono_wasm_runtime_ready(): void {
         debugger;
 }
 
-export function mono_wasm_fire_debugger_agent_message(): void {
+export function mono_wasm_fire_debugger_agent_message_with_data_to_pause(base64String: string): void {
+    //keep this console.assert, otherwise optimization will remove the assignments
+    console.assert(true, `mono_wasm_fire_debugger_agent_message_with_data ${base64String}`);
     // eslint-disable-next-line no-debugger
     debugger;
 }
 
+export function mono_wasm_fire_debugger_agent_message_with_data(data: number, len: number): void {
+    const base64String = toBase64StringImpl(new Uint8Array(Module.HEAPU8.buffer, data, len));
+    mono_wasm_fire_debugger_agent_message_with_data_to_pause(base64String);
+}
+
 export function mono_wasm_add_dbg_command_received(res_ok: boolean, id: number, buffer: number, buffer_len: number): void {
-    const assembly_data = new Uint8Array(Module.HEAPU8.buffer, buffer, buffer_len);
-    const base64String = toBase64StringImpl(assembly_data);
+    const dbg_command = new Uint8Array(Module.HEAPU8.buffer, buffer, buffer_len);
+    const base64String = toBase64StringImpl(dbg_command);
     const buffer_obj = {
         res_ok,
         res: {
index b0cae05..b0dfbc2 100644 (file)
@@ -27,8 +27,8 @@ interface MonoArray extends MonoObject {
 interface MonoObjectRef extends ManagedPointer {
     __brandMonoObjectRef: "MonoObjectRef";
 }
-declare type MemOffset = number | VoidPtr | NativePointer | ManagedPointer;
-declare type NumberOrPointer = number | VoidPtr | NativePointer | ManagedPointer;
+type MemOffset = number | VoidPtr | NativePointer | ManagedPointer;
+type NumberOrPointer = number | VoidPtr | NativePointer | ManagedPointer;
 interface WasmRoot<T extends MonoObject> {
     get_address(): MonoObjectRef;
     get_address_32(): number;
@@ -60,7 +60,7 @@ interface WasmRootBuffer {
 /**
  * @deprecated Please use methods in top level API object instead
  */
-declare type BINDINGType = {
+type BINDINGType = {
     /**
      * @deprecated Please use [JSExportAttribute] instead
      */
@@ -137,7 +137,7 @@ declare type BINDINGType = {
 /**
  * @deprecated Please use methods in top level API object instead
  */
-declare type MONOType = {
+type MONOType = {
     /**
      * @deprecated Please use setEnvironmentVariable() instead
      */
index 8eafb1d..ed026d8 100644 (file)
@@ -61,7 +61,7 @@ const linked_functions = [
 
     // mini-wasm-debugger.c
     "mono_wasm_asm_loaded",
-    "mono_wasm_fire_debugger_agent_message",
+    "mono_wasm_fire_debugger_agent_message_with_data",
     "mono_wasm_debugger_log",
     "mono_wasm_add_dbg_command_received",
     "mono_wasm_set_entrypoint_breakpoint",
index 8329035..733bdd9 100644 (file)
@@ -2,7 +2,8 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 import MonoWasmThreads from "consts:monoWasmThreads";
-import { mono_wasm_fire_debugger_agent_message, mono_wasm_debugger_log, mono_wasm_add_dbg_command_received, mono_wasm_set_entrypoint_breakpoint } from "./debug";
+import { mono_wasm_fire_debugger_agent_message_with_data, mono_wasm_fire_debugger_agent_message_with_data_to_pause, mono_wasm_debugger_log, mono_wasm_add_dbg_command_received, mono_wasm_set_entrypoint_breakpoint } from "./debug";
+
 import { mono_wasm_release_cs_owned_object } from "./gc-handles";
 import { mono_wasm_load_icu_data, mono_wasm_get_icudt_name } from "./icu";
 import { mono_wasm_bind_cs_function } from "./invoke-cs";
@@ -48,10 +49,10 @@ export function export_linker(): any {
 
         // mini-wasm-debugger.c
         mono_wasm_asm_loaded,
-        mono_wasm_fire_debugger_agent_message,
         mono_wasm_debugger_log,
         mono_wasm_add_dbg_command_received,
-
+        mono_wasm_fire_debugger_agent_message_with_data,
+        mono_wasm_fire_debugger_agent_message_with_data_to_pause,
         // mono-threads-wasm.c
         schedule_background_exec,
 
index 37f6dd6..299c253 100644 (file)
@@ -27,7 +27,7 @@ const terserConfig = {
     mangle: {
         // because of stack walk at src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs
         // and unit test at src\libraries\System.Runtime.InteropServices.JavaScript\tests\System.Runtime.InteropServices.JavaScript.Legacy.UnitTests\timers.mjs
-        keep_fnames: /(mono_wasm_runtime_ready|mono_wasm_fire_debugger_agent_message|mono_wasm_set_timeout_exec)/,
+        keep_fnames: /(mono_wasm_runtime_ready|mono_wasm_fire_debugger_agent_message_with_data_to_pause|mono_wasm_set_timeout_exec)/,
         keep_classnames: /(ManagedObject|ManagedError|Span|ArraySegment|WasmRootBuffer|SessionOptionsBuilder)/,
     },
     format: {