Skip to content

Commit 8105d41

Browse files
committed
cleanup
1 parent 240ed40 commit 8105d41

2 files changed

Lines changed: 74 additions & 14 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,21 @@ and this project adheres to
1818

1919
### Added
2020

21-
- Compilation warnings for global virtual clock operations to prevent race conditions
21+
- Compilation warnings for global virtual clock operations to prevent race
22+
conditions
2223

2324
### Changed
2425

2526
- Time backend is now internal and transparent to client code
26-
- VirtualTimeGenServer and VirtualTimeGenStateMachine functions now emit warnings when using global clock injection
27+
- VirtualTimeGenServer and VirtualTimeGenStateMachine functions now emit
28+
warnings when using global clock injection
2729

2830
## [0.5.0-rc.2] - 2025-01-27
2931

3032
### Added
3133

32-
- VirtualTimeGenStateMachine: `start_link/3`, `start/3`, `call/3`, `cast/2`, `stop/3` functions
34+
- VirtualTimeGenStateMachine: `start_link/3`, `start/3`, `call/3`, `cast/2`,
35+
`stop/3` functions
3336

3437
### Fixed
3538

@@ -46,13 +49,15 @@ and this project adheres to
4649

4750
### Added
4851

49-
- Ractor (Rust) code generator with [Ractor](https://github.com/slawlor/ractor) framework
52+
- Ractor (Rust) code generator with [Ractor](https://github.com/slawlor/ractor)
53+
framework
5054
- Single-file generator examples for Ractor and VLINGO
5155
- Documentation for Ractor generator
5256

5357
### Fixed
5458

55-
- Separated generated interface code from customizable implementation code across all generators
59+
- Separated generated interface code from customizable implementation code
60+
across all generators
5661
- Fixed Mermaid flowchart reports missing edges for dynamic sends
5762

5863
## [0.3.0] - 2025-10-14
@@ -92,7 +97,8 @@ and this project adheres to
9297

9398
- Code generators for CAF (C++), Pony, Phony (Go), and VLINGO (Java)
9499
- Mermaid report generator module
95-
- Full GenServer callback support including `handle_continue/2` and call timeout handling
100+
- Full GenServer callback support including `handle_continue/2` and call timeout
101+
handling
96102
- Termination conditions for simulations based on actor state
97103
- Enhanced Mermaid diagrams with sequence diagram features
98104
- Dining philosophers example

lib/virtual_clock.ex

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -550,11 +550,43 @@ defmodule VirtualClock do
550550
end
551551
end
552552

553+
# Check if a process should be tracked for ACKs
554+
# Only GenServers using VirtualTimeGenServer or VirtualTimeGenStateMachine wrappers will ACK
555+
defp should_track_for_ack?(pid) do
556+
case Process.info(pid, :dictionary) do
557+
{:dictionary, dict} when is_list(dict) ->
558+
has_virtual_clock?(dict) && genserver_actor?(pid, dict)
559+
560+
_ ->
561+
false
562+
end
563+
end
564+
565+
defp has_virtual_clock?(dict) do
566+
Keyword.has_key?(dict, :virtual_clock)
567+
end
568+
569+
defp genserver_actor?(pid, dict) do
570+
case Keyword.get(dict, :"$initial_call") do
571+
{VirtualTimeGenServer.Wrapper, _, _} -> true
572+
{VirtualTimeGenStateMachine.Wrapper, _, _} -> true
573+
_ -> check_current_function_for_gen_server(pid)
574+
end
575+
end
576+
577+
defp check_current_function_for_gen_server(pid) do
578+
case Process.info(pid, :current_function) do
579+
{:current_function, {mod, _, _}} -> mod in [:gen_server, :gen_statem]
580+
_ -> false
581+
end
582+
end
583+
553584
defp advance_loop(state, target_time, from) do
554585
# Calculate adaptive timeout based on time advance
555586
# For large advances, use longer timeout (max 30 seconds)
556587
time_advance = target_time - state.current_time
557-
ack_timeout_ms =
588+
589+
ack_timeout_ms =
558590
if time_advance > 100_000 do
559591
# For advances > 100s, use adaptive timeout
560592
min(trunc(time_advance / 1000), 30_000)
@@ -565,7 +597,11 @@ defmodule VirtualClock do
565597
# Set up timeout for ack wait - only if we have pending acks
566598
ack_timeout_ref =
567599
if MapSet.size(state.pending_acks) > 0 do
568-
Process.send_after(self(), {:ack_timeout, MapSet.to_list(state.pending_acks)}, ack_timeout_ms)
600+
Process.send_after(
601+
self(),
602+
{:ack_timeout, MapSet.to_list(state.pending_acks)},
603+
ack_timeout_ms
604+
)
569605
else
570606
nil
571607
end
@@ -582,8 +618,16 @@ defmodule VirtualClock do
582618
if MapSet.size(new_state.pending_acks) > 0 do
583619
# Still waiting for actors - store caller and wait for acks with adaptive timeout
584620
time_advance = target_time - state.current_time
585-
ack_timeout_ms = if time_advance > 100_000, do: min(trunc(time_advance / 1000), 30_000), else: 2000
586-
Process.send_after(self(), {:ack_timeout, MapSet.to_list(new_state.pending_acks)}, ack_timeout_ms)
621+
622+
ack_timeout_ms =
623+
if time_advance > 100_000, do: min(trunc(time_advance / 1000), 30_000), else: 2000
624+
625+
Process.send_after(
626+
self(),
627+
{:ack_timeout, MapSet.to_list(new_state.pending_acks)},
628+
ack_timeout_ms
629+
)
630+
587631
{:noreply, %{new_state | advance_caller: from, target_time: target_time}}
588632
else
589633
# No pending acks - advance complete!
@@ -599,10 +643,17 @@ defmodule VirtualClock do
599643
if ack_timeout_ref, do: Process.cancel_timer(ack_timeout_ref)
600644

601645
# Process events at next_time - track who we're sending to for acks
602-
actor_pids =
603-
Enum.map(triggered, fn event ->
646+
# Only track GenServer actors that will send ACKs, not regular processes
647+
{actor_pids, _} =
648+
Enum.reduce(triggered, {[], []}, fn event, {ack_actors, regular_processes} ->
649+
# Send message immediately
604650
VirtualTimeGenServer.send_immediately(event.dest, event.message)
605-
event.dest
651+
652+
if should_track_for_ack?(event.dest) do
653+
{[event.dest | ack_actors], regular_processes}
654+
else
655+
{ack_actors, [event.dest | regular_processes]}
656+
end
606657
end)
607658

608659
# Track pending acks and update time
@@ -614,7 +665,10 @@ defmodule VirtualClock do
614665
# Actors are processing - store caller and wait for all acks
615666
# Set up adaptive timeout for the new acks
616667
time_advance = target_time - next_time
617-
ack_timeout_ms = if time_advance > 100_000, do: min(trunc(time_advance / 1000), 30_000), else: 2000
668+
669+
ack_timeout_ms =
670+
if time_advance > 100_000, do: min(trunc(time_advance / 1000), 30_000), else: 2000
671+
618672
Process.send_after(self(), {:ack_timeout, MapSet.to_list(new_pending)}, ack_timeout_ms)
619673
{:noreply, %{new_state | advance_caller: from, target_time: target_time}}
620674
else

0 commit comments

Comments
 (0)