102 Commits

Author SHA1 Message Date
Yukihiro "Matz" Matsumoto 94bd329afe Merge pull request #6871 from hasumikin/fix/execute_task
Fix execute_task() so unhandled task exceptions become task results
2026-05-29 07:05:36 +09:00
Yukihiro "Matz" Matsumoto be36b67a12 mruby-task: clear dead stack slots when marking preempted tasks
mrb_task_mark_all marked a task's live registers but, unlike
mark_context_stack in gc.c, never cleared the slots above the live
range. When a preempted task's live range later shrank (a frame had
returned), the stale object pointers left in those slots were neither
marked nor cleared: the objects were swept while the pointers survived.
Re-entering the same frame reused those slots, and the next mark of the
resumed task hit a freed object, tripping the MRB_TT_FREE assertion in
mrb_gc_mark.

Clear the dead slots after marking, exactly as mark_context_stack does
for the running context.

Fixes #6870.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-29 05:59:14 +09:00
HASUMI Hitoshi a502be4f9f Fix fallback when abnormal error happens
It is not likely happens but if happened, in pattern 1, the whole
process abort when mrb->jmp is NULL.
Instead, make the status MRB_TASK_STOPPED and delegate following
logic of "Handle task termination"
2026-05-29 01:30:24 +09:00
HASUMI Hitoshi f1232334c0 Fix execute_task() so unhandled task exceptions become task results
This patch fixes a bug that c09196c introduced.

## Background

`mrb_task_run()` has two usage patterns:

1. Called directly from `main()` as the top-level scheduler (PicoRuby and R2P2). There is no surrounding C exception handler, so mrb->jmp is NULL on entry
2. Called from Ruby code via Task.run, bootstrapped on top of mruby's regular call chain. mrb->jmp is non-NULL

Historically, an unhandled exception raised inside a task body was turned into the task's result value by `mrb_vm_exec()`: the L_RAISE path walked callinfo down to cibase, ran `fiber_terminate()`, and - because c->vmexec was TRUE and prev_jmp was NULL in pattern 1 - took `return mrb_obj_value(mrb->exc)`.
That value landed in t->result and could be read back through `mrb_task_value()` / `join()`.

## What c09196c broke

It consider only pattern 2 and wrapped `mrb_task_run()` in a protect frame (MRB_TRY / mrb_protect_error) to guarantee that loop_running is cleared on exception.
As a side effect, mrb->jmp is now always non-NULL while a task body is executing, so the L_RAISE path takes `MRB_THROW(prev_jmp)` instead of returning the exception value.

In pattern 2 this merely changed the semantics (exceptions started propagating out of `Task.run` instead of being stored as task results).
In pattern 1 it was FATAL: the throw unwound to mrb_task_run's catch handler, which called `mrb_exc_raise()` to re-propagate, and with no outer jmpbuf this aborted the process.
PicoRuby/R2P2 could no longer retrieve task exceptions via `mrb_task_value()`.

## Fix

Restore the "task exception becomes task result" contract uniformly for both patterns, independent of mrb->jmp:

* Add `mrb_task_state.exception_as_result`. When set, `mrb_vm_exec()`'s non-root_c L_RAISE branch returns the exception as a value even if prev_jmp is non-NULL, instead of throwing

* `execute_task_vm()` raises the flag around `mrb_vm_exec()`, captures the exception into `t->result`, and clears `mrb->exc`

* Wrap `execute_task_vm()` in `mrb_protect_error()` as a safety net for rare paths that still unwind via MRB_THROW (e.g. CINFO_SKIP frames). exception_as_result is reset both at the end of the body and immediately after `mrb_protect_error()` returns, so a caught throw does not leave the llag set

* Expose `Task#value` to retrieve t->result from Ruby, since Task#join cannot deliver the value through its return path under cooperative scheduling

* Add a test asserting that `Task#join` on a task that raised returns the exception object, matching the pre-c09196c observable behavior

## Notes

The "task exception becomes task result" semantics match the mruby/c's rrt0.c and the spirit of CRuby's Thread (an unhandled exception in a thread does not kill the scheduler / process; it surfaces when the thread is joined).
The visible API shape still differs from CRuby - `Task#join` here returns the exception object rather than re-raising it - but the scheduler is no longer destabilized by task errors in either invocation pattern.
2026-05-29 01:25:47 +09:00
HASUMI Hitoshi e0d6d39ce9 Write validate flag (status) after setting timeslice
`mrb_tick()` observes `status == RUNNING` before touching `timeslice`,
so initializing `timeslice` first avoids exposing a partially initialized running state
2026-05-28 19:18:17 +09:00
HASUMI Hitoshi 564726c44f t->result now can be marked unconditionally 2026-05-28 19:16:04 +09:00
HASUMI Hitoshi 5e669560eb Amend field position to pack effectively 2026-05-28 19:07:20 +09:00
HASUMI Hitoshi 030a08092a Separate the union of timeslice and result in struct mrb_task
Bug scenario:
* VM returns an Exception `t->state.result = mrb_vm_exec(...);`
* Despite task is still MRB_TASK_STATUS_RUNNING, IRQ triggered by chance and `mrb_tick()` executes `t->state.timeslice--;`
* But the same memory area already holds the `result`, `timeslice--` reduces `result.value.p`'s top byte

The fix is to separate `timeslice` and `result` into different fields.
I have considered improving critical sections, but I ended up with this patch because I believe it is widely effective and less error-prone.
2026-05-28 18:48:13 +09:00
Yukihiro "Matz" Matsumoto c09196ca36 mruby-task: switch mrb_task_run to mrb_protect_error
mruby/throw.h is documented as a core-internal header that should not
be included from mrbgems or user code, and under MRB_USE_CXX_EXCEPTION
or MRB_USE_CXX_ABI the MRB_TRY/MRB_CATCH macros expand to C++
exception syntax that does not compile in a C source file. The
wrapping added in #6866 (commit ee82a7fcc6) accidentally tripped that
constraint.

Drop the throw.h include and use mrb_protect_error() from
mruby/error.h instead. The helper takes a body function plus
userdata, runs it under its own jmpbuf, and reports whether an
exception was caught. We re-raise via mrb_exc_raise so the visible
behavior matches the previous code: loop_running is cleared on both
success and exception, and an exception propagates back out.

Refs #6866.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-28 12:10:08 +09:00
Yukihiro "Matz" Matsumoto dfc542dbc5 mruby-task: terminate suspended task in suspend test
The "Task#suspend doesn't raise" test left its task parked in
q_suspended_. A later test ("Task.run inside Task.run is a noop")
calls Task.run, and the scheduler will not exit its loop while any
task sits in the suspended (or waiting) queue, so the whole run
hung.

Terminate the task at the end of the suspend test so it does not
leak into the shared scheduler state the next test depends on. This
fixes the hang at its source rather than scrubbing leaked tasks from
the consumer side.

Refs #6866, #6867.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-28 12:02:07 +09:00
0x1eef 4bdde3a0a8 task: add test 2026-05-27 23:27:51 -03:00
0x1eef ee82a7fcc6 fix: wrap mrb_task_run in MRB_TRY/MRB_CATCH 2026-05-27 23:19:43 -03:00
0x1eef 819156e678 task: return nil when given a nested call to Task.run
When you try to start an event loop inside an event loop,
the mruby process will SIGSEGV:

```ruby
Task.new { Task.run }
Task.run
```

This change turns the second call to `Task.run` into a noop
that returns nil instead.

Fix #6865
2026-05-27 23:10:09 -03:00
Yukihiro "Matz" Matsumoto 4f398f6126 mruby-task: prefix queue helpers with mrb_task_
`q_insert_task` and `q_delete_task` were exporting bare `q_*` names
from libmruby.a -- single-letter prefixes don't belong to the gem's
namespace and risk colliding with anything else linked in.

Rename to `mrb_task_q_insert` / `mrb_task_q_delete`, matching the
`mrb_task_*` convention already used for the rest of the gem's
externally visible symbols. Callers in task.c and task_queue.c are
updated to the new names.

Closes #6858.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-25 06:48:13 +09:00
Yukihiro "Matz" Matsumoto 3e676af568 Merge pull request #6836 from hasumikin/fix/task
Fix mruby-task: keep join waiter wakeup under one IRQ critical section
2026-05-18 16:26:31 +09:00
HASUMI Hitoshi c4d205496d Fix mruby-task: keep join waiter wakeup under one IRQ critical section 2026-05-18 16:11:04 +09:00
Yukihiro "Matz" Matsumoto 1317a8bf5d Merge pull request #6835 from hasumikin/task-queue
Introduce Task::Queue
2026-05-18 15:38:16 +09:00
HASUMI Hitoshi a1a77e5acc Call task_check_scheduler_lock in queue_pop_try to avoid dead lock 2026-05-18 15:30:44 +09:00
HASUMI Hitoshi c9ae2524ee Cache Task::Error in task_error_class_ iniailized in mrb_init_task_queue() 2026-05-18 15:26:38 +09:00
HASUMI Hitoshi 458078f10e Introduce Task::Queue
- This patch implements `Task::Queue` mirroring `Thread::Queue` in CRuby.
- Producer/Consumer pattern is now possible with no polling

## No Top-level pollution
- No top-level `Queue` defined
- No `TaskError`. Instead, `Task::Error` happens when `Task::Queue#pop(true)` when empty
- No `ClosedQueueError`. `Task::Error` also happens when pushing to closed queue

## Future Work
- `Task::SizedQueue`
2026-05-18 14:23:42 +09:00
Yukihiro "Matz" Matsumoto d1bffb902e Merge pull request #6834 from Asmod4n/glib_hal
mruby-task GLib HAL
2026-05-17 23:31:33 +09:00
Asmod4n 17858cc5bd mruby-task GLib HAL
Lets mruby-task embed cleanly in any GLib-based event loop -- GTK,
libsoup, GStreamer, or anything else built on GMainContext. Tasks
become regular GSources, so the scheduler runs alongside whatever
else is on the loop without polling or busy-waiting.

Sleeping tasks cost zero CPU: the HAL parks until the next wakeup
deadline rather than ticking on a fixed cadence. Multiple mrb_states
on the same thread share one dispatcher and one ticker. Preemption,
Task.run, sleeper wakes, and foreign-loop integration all use the
same primitives, so embedders can mix Task.run with g_main_loop_run
freely.

ref mruby#6825
2026-05-17 13:44:20 +02:00
Yukihiro "Matz" Matsumoto 67f137e201 gem.rb: hal_pattern for external HAL provider override
A gem can declare

  spec.hal_pattern = /\Ahal-.*-task\z/

to indicate that another gem whose name matches the pattern (and
which depends on this gem for headers) replaces the built-in
ports/<conf.ports>/ HAL implementation.  After all gems are set
up, List#resolve_external_hal! drops the target's ports/* objs
from its object list so the matching gem supplies the HAL
symbols.  Two or more matches is reported as a build error.

This restores the pre-be6413f0d8 ability to maintain an
out-of-tree HAL via add_dependency + naming convention, without
reintroducing the "HAL information scattered across gems"
problem: the parent gem still owns the scheduler, headers, and
bundled posix/win ports; external HAL gems are an explicit,
opt-in override.

Declare /\Ahal-.*-task\z/ for mruby-task -- the same naming
pattern used before be6413f0d8.

ref #6825

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-17 17:33:26 +09:00
Hendrik 8b63ddbfd1 Refactor task wakeup logic and add comments
Enhance wakeup logic for sleeping tasks to handle UINT32_MAX sentinel case and prevent race conditions with mrb_tick.

This enables tickless Task hals to be build.
2026-05-17 08:14:58 +02:00
Hendrik 2668619143 Improve wakeup tick condition check
We got a off by one error here, checking against UINT32_MAX doesn't silently set a wrong value.

When running the task mgem with a busy loop this doesn't cause an issue, but with a tickless timer only one timer ever gets fired and then the task mgem stops working.
2026-05-17 07:31:08 +02:00
Yukihiro "Matz" Matsumoto 3e5e84e391 mruby-task: update README and headers for ports-based HAL layout
The HAL was integrated into mruby-task/ports/{posix,win}/ in
be6413f0d8 and the function names were updated in 610ff67906, but
the docs and one header still described the old separate-gem layout:

  - mrbgems/mruby-task/README.md described hal-posix-task and
    hal-win-task as separate gems, used the pre-rename function
    names (mrb_task_hal_*), and omitted mrb_hal_task_sleep_us.
    Rewrote the HAL section to match the current ports/ model.

  - mrbgems/mruby-task/include/task.h had three orphan declarations
    (mrb_task_hal_init / _final / _idle_cpu) from before the
    rename. Removed; the real declarations are in task_hal.h.

  - mrbgems/mruby-task/include/task_hal.h had a comment referring
    to the removed hal-* gems.

  - doc/guides/amalgamation.md listed hal-posix-io and hal-posix-task
    as platform-specific gems alongside mruby-io and mruby-task; both
    are now ports under the parent gem.

Reported by Asmod4n in #6825.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-14 13:48:55 +09:00
Yukihiro "Matz" Matsumoto 2ff6563484 mruby-task: restore Windows link library dropped during HAL migration
Commit be6413f0d86a ("mruby-task: migrate HAL to ports/ directories")
moved the Windows HAL source into mruby-task/ports/win/ but dropped
the linker.libraries declaration that previously lived in
hal-win-task/mrbgem.rake. Both mingw and MSVC builds now fail to link
task_hal.o/obj with undefined references to timeBeginPeriod,
timeEndPeriod, timeSetEvent, and timeKillEvent. Re-declare winmm
under a for_windows? guard.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 23:16:21 +09:00
Yukihiro "Matz" Matsumoto be6413f0d8 mruby-task: migrate HAL to ports/ directories
Move hal-posix-task and hal-win-task into
mruby-task/ports/posix/ and mruby-task/ports/win/.
Remove HAL auto-detection logic from mrbgem.rake. Update
cosmopolitan.rb to use conf.ports :posix instead of explicit
hal-* gem references.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:37 +09:00
Yukihiro "Matz" Matsumoto 16fbd56e4b mruby-task: extract task_create_common() from Task.new and mrb_create_task()
both functions shared identical task allocation, context
initialization, queue insertion, and priority preemption logic.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-23 19:25:25 +09:00
Yukihiro "Matz" Matsumoto 8956c5abb5 mruby.h: include mruby/presym.h for all source files
Since presym is now mandatory, mruby.h includes presym.h so that
MRB_SYM() macros are available everywhere without explicit include.
Remove redundant #include <mruby/presym.h> from all source files.

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-09 16:50:58 +09:00
Yukihiro "Matz" Matsumoto b460554d33 vm.c: generalize pre-dispatch argument count check for C methods
Replace check_method_noarg() with check_argument_count() that validates
min <= argc <= max using the full aspec stored in mrb_method_t.flags.
This catches ArgumentError earlier at dispatch time, before entering
the C function.

The old check only handled the special case of aspec==0 (NOARG).
The new check extracts REQ, OPT, REST, POST, KEY, and KDICT from
the aspec and validates accordingly. Keyword hash is counted as
a positional arg only when the method doesn't accept keywords.

Remove MRB_METHOD_NOARG_P macro from proc.h (subsumed by aspec check).
Fix 15 incorrect aspec declarations across the codebase that were
exposed by the stricter enforcement.

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-20 14:25:48 +09:00
HASUMI Hitoshi 40d6e2e9a4 Update mrbgems/mruby-task/src/task.c
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-13 14:34:34 +09:00
HASUMI Hitoshi ee610cdbb6 Set initial task receiver to top_self for stability
The current implementation of `task_init_context` inheriting a receiver from the parent task is unstable and causes critical faults, especially on microcontrollers.

- It leads to a HardFault on devices like Raspberry Pi Pico 2 by accessing a potentially NULL `mrb->c->ci`.
- Even when `mrb->c->ci` is not NULL, this incomplete context copy causes other memory errors (SEGV).

This patch reverts to the safer, previous behavior, that I implemented in picoruby/picoruby, of always initializing a new task's receiver to `top_self`, ensuring predictable and
robust operation.
The issue was likely masked on POSIX systems due to the unpredictable nature of undefined behavior.
2026-02-13 13:59:48 +09:00
HASUMI Hitoshi cfcd86fd9b Fix mrb_task_run to prevent returning unexpectedly
Old code:

```c
t = q_ready_;

/* No task ready - check if all tasks are done */
if (!t) {
  /* If there are tasks waiting or suspended, idle */
  if (q_waiting_ || q_suspended_) {
    mrb_hal_task_idle_cpu(mrb);
    continue;
```

IRQ possibly happens between `t = q_ready_;` and `if (q_waiting_ || q_suspended_) {` and, for example, a waiting task may move to the ready queue.
As a result, the infinite loop in mrb_task_run unexpectedly breaks in spite of not all the task is dormant.
This patch fixes the issue above by setting the `exitting` condition with a critical section.
2026-02-13 13:34:54 +09:00
Yukihiro "Matz" Matsumoto c25b562256 Merge pull request #6706 from Asmod4n/patch-4 2026-01-20 13:28:25 +09:00
Hendrik 3d5bb929ab Refactor task class to use symbol IDs 2026-01-17 19:31:24 +01:00
HASUMI Hitoshi 219588091b Improve task.c code clarity and fix potential GC issue
- Add mrb_gc_protect() after arena_restore to prevent result from being collected before returning to caller
- Add comment to suspend_task_internal explaining why WAITING and DORMANT tasks can also be suspended
- Move argc/argv cast at the beginning of function with comment
2026-01-16 08:56:49 +09:00
HASUMI Hitoshi e7d6def808 Refactor Task#suspend,terminate,resume
- Fix inconsistency of MRB_API functions and Ruby methods
- Get rid of duplication
- Adjust error handling
2026-01-14 15:48:36 +09:00
HASUMI Hitoshi 63dd1832bc Improve memory management of mrb_execute_proc_synchronously
Wrap sync task by mrb_gc_arena_save/restore to release objects from arena
that a sync task allocated so that they can be freed in GC cycle
2026-01-14 14:12:51 +09:00
HASUMI Hitoshi 8a0263026e Add scheduler_lock check
And refactoring to consolidate duplicate code

ref PR #6699
2026-01-12 13:44:54 +09:00
HASUMI Hitoshi 0a21eef938 Fix mruby-task for PicoRuby Integration
With this PR, I can remove the original task.c in picoruby/picoruby and future development will be much easier.

## Add

### General

- C API functions exported with MRB_API for external integration:
  - mrb_execute_proc_synchronously() for synchronous proc execution
  - Task control APIs (mrb_create_task, mrb_suspend_task, mrb_resume_task, mrb_terminate_task, mrb_stop_task, mrb_task_value, mrb_task_status)
  - Task context management APIs for picoruby-sandbox (mrb_task_init_context, mrb_task_reset_context, mrb_task_proc_set)
  - Task.tick class method to get current tick count
- Comprehensive C API documentation with WASM integration examples

### For PicoRuby.wasm

- WASM/Emscripten support: Disable SIGALRM timer when __EMSCRIPTEN__ is defined, as JavaScript handles tick calls via setInterval
- Scheduler lock mechanism to prevent asynchronous task operations during synchronous execution (scheduler_lock counter in mrb_task_state)
- mrb_task_run_once() for single-step execution (event loop integration)

## Fix

### task.c
- Replace MRB_FIBER_TERMINATED with MRB_TASK_STOPPED just for clarity
- Allow suspending DORMANT and WAITING tasks in mrb_task_suspend (See comment in the source)
- Task context initialization by removing dummy callinfo push/pop

*NOTE*

With the dummy callinfo code that I deleted, IRB in PicoRuby ended SEGV.
If that code is mandatory, we need to discuss how to solve my problem.

### vm.c
- Handle MRB_TASK_CREATED status in VM's NORMAL_RETURN phase to properly stop tasks

----

These changes are necessary to make PicoRuby work.
Nevertheless, even with this patch, MicroRuby for Raspberry Pi Pico 2 is still unstable.
I would like to merge this PR anyway to make development easier by involving the PicoRuby community.
2026-01-11 17:07:04 +09:00
Yukihiro "Matz" Matsumoto 7e2f20573c mruby-task: combine variable declaration with initialization 2025-11-08 14:14:44 +09:00
Yukihiro "Matz" Matsumoto ad51bf848b mrbgem.rake: simplify hal selection logic
remove redundant visualcpp and mingw checks since for_windows? already
detects all windows builds including visual c++ and mingw.

ref #6653

Co-authored-by: Claude <noreply@anthropic.com>
2025-10-27 11:11:27 +09:00
Yukihiro "Matz" Matsumoto 28b567ae78 Merge pull request #6653 from dearblue/mingw
Improve HAL-related components for MinGW
2025-10-27 10:56:28 +09:00
Yukihiro "Matz" Matsumoto def463962e mruby-task: combine variable declaration with initialization 2025-10-26 19:05:01 +09:00
dearblue ea215bc19c Fixed HAL auto-detection order
Because MinGW was not recognized as Windows during cross-builds.
2025-10-25 21:06:16 +09:00
Yukihiro "Matz" Matsumoto dc7c6ed7a7 mruby-fiber,mruby-task: increase stack init size for 32-bit msvc
increase fiber_stack_init_size and task_stack_init_size from 16 to 64
to fix crashes on 32-bit msvc builds. git bisect identified commit
3246dd2 (which reduced sizes from 64 to 16) as causing the issue.
empirical testing shows 48 fails intermittently but 64 is stable on
32-bit msvc, likely due to different alignment or initialization
overhead on 32-bit platforms.

Co-authored-by: Claude <noreply@anthropic.com>
2025-10-18 23:34:10 +09:00
Yukihiro "Matz" Matsumoto e540547413 mruby-task: use hal-win-task for mingw builds
mingw provides posix compatibility for file i/o but not for signal
handling. hal-posix-task relies on SIGALRM, setitimer(), and
sigprocmask() which are not available on windows even through mingw.

changed hal selection for mruby-task to use hal-win-task for mingw,
while mruby-dir, mruby-io, and mruby-socket correctly use posix hals
for mingw since those features are supported.

Co-authored-by: Claude <noreply@anthropic.com>
2025-10-16 19:00:06 +09:00
Yukihiro "Matz" Matsumoto feca90ceab hal: fix selection to use toolchain instead of RUBY_PLATFORM
when building with MSVC on Windows, RUBY_PLATFORM (from the Ruby
installation running rake) may indicate "mingw" if Ruby was installed
via RubyInstaller, causing incorrect selection of POSIX HALs instead
of Windows HALs.

fixed by checking spec.build.primary_toolchain first:
- if toolchain is "visualcpp", select Windows HALs
- otherwise fall through to existing platform checks

this ensures MSVC builds use hal-win-* gems even when Ruby itself
was installed with MinGW.

affected gems:
- mruby-dir
- mruby-io
- mruby-socket
- mruby-task

Co-authored-by: Claude <noreply@anthropic.com>
2025-10-16 18:20:32 +09:00
Yukihiro "Matz" Matsumoto 34205d6ba3 mruby-task: fix integer conversion warning by using uint32_t for sleep functions
change sleep_us_impl and sleep_ms_impl parameters from mrb_int to uint32_t.
this makes the type requirement explicit and resolves msvc warning c4244.
all type conversions happen at ruby boundary functions after validation.

Co-authored-by: Claude <noreply@anthropic.com>
2025-10-16 16:34:01 +09:00