Permalink
Browse files

Make 'delivery before receive' uninstrumentable

Before letting an instrumented process receive a message, Concuerror
places it in the process' mailbox. Since what follows is a receive
statement that cannot be intercepted (it has already been
instrumented) this delivery must also not be possible to intercept.

No other messages should arrive before the predicted receive statement
has happened, so the notification for the receive *has* to be sent
after the receive is completed (otherwise the scheduler's next message
would race with it). This introduces the 'delayed_notification'
mechanism: before waiting for the next scheduler message, notify about
the receive that was completed.
  • Loading branch information...
aronisstav committed Oct 14, 2017
1 parent 85667aa commit c1c641ed27b01bcb4047fc8f63fe61fa3df63ff2
@@ -70,6 +70,7 @@
-record(concuerror_info, {
after_timeout :: 'infinite' | integer(),
delayed_notification = none :: 'none' | {'true', term()},
demonitors = [] :: [reference()],
ets_tables :: ets_tables(),
exit_by_signal = false :: boolean(),
@@ -158,7 +159,7 @@ hijack_backend(#concuerror_info{processes = Processes} = Info) ->
-type instrumented_return() :: 'doit' |
{'didit', term()} |
{'error', term()} |
'skip_timeout' |
{'skip_timeout', 'false' | {'true', term()}} |
'unhijack'.
-spec instrumented(Tag :: instrumented_tag(),
@@ -1258,13 +1259,16 @@ handle_receive(MessageOrAfter, PatternFun, Timeout, Location, Info) ->
end,
Notification =
NextEvent#event{event_info = ReceiveEvent, special = Special},
case CreateMessage of
{ok, D} ->
?debug_flag(?receive_, {deliver, D}),
self() ! D;
false -> ok
end,
{skip_timeout, notify(Notification, UpdatedInfo)}.
AddMessage =
case CreateMessage of
{ok, D} ->
?debug_flag(?receive_, {deliver, D}),
{true, D};
false ->
false
end,
{{skip_timeout, AddMessage}, delay_notify(Notification, UpdatedInfo)}.
has_matching_or_after(_, _, _, unhijack, _) ->
unhijack;
@@ -1348,6 +1352,9 @@ notify(Notification, #concuerror_info{scheduler = Scheduler} = Info) ->
Scheduler ! Notification,
Info.
delay_notify(Notification, Info) ->
Info#concuerror_info{delayed_notification = {true, Notification}}.
-spec process_top_loop(concuerror_info()) -> no_return().
process_top_loop(Info) ->
@@ -1413,6 +1420,11 @@ wait_process(Pid, Timeout) ->
?crash({process_did_not_respond, Timeout, Pid})
end.
process_loop(#concuerror_info{delayed_notification = {true, Notification},
scheduler = Scheduler} = Info) ->
Scheduler ! Notification,
process_loop(Info#concuerror_info{delayed_notification = none});
process_loop(#concuerror_info{notify_when_ready = {Pid, true}} = Info) ->
?debug_flag(?loop, notifying_parent),
Pid ! ready,
@@ -62,7 +62,12 @@ inspect(Tag, Args, Location) ->
Timeout
end;
retry -> inspect(Tag, Args, Location);
skip_timeout -> 0;
{skip_timeout, CreateMessage} ->
case CreateMessage of
false -> ok;
{true, D} -> self() ! D
end,
0;
{didit, Res} -> Res;
unhijack ->
erase(concuerror_info),
@@ -0,0 +1,43 @@
Concuerror v0.17 ('1893fb5') started at 14 Oct 2017 20:56:42
Options:
[{after_timeout,infinity},
{assertions_only,false},
{assume_racing,false},
{depth_bound,500},
{disable_sleep_sets,false},
{dpor,optimal},
{entry_point,{racing_after,test,[]}},
{exclude_module,[]},
{files,["/home/stavros/git/Concuerror/tests/suites/basic_tests/src/racing_after.erl"]},
{ignore_error,[]},
{instant_delivery,true},
{interleaving_bound,infinity},
{keep_going,true},
{non_racing_system,[]},
{print_depth,20},
{quiet,true},
{scheduling,round_robin},
{scheduling_bound_type,none},
{show_races,false},
{strict_scheduling,false},
{symbolic_names,true},
{timeout,infinity},
{treat_as_normal,[]},
{use_receive_patterns,false}]
################################################################################
Exploration completed!
No errors found!
################################################################################
Tips:
--------------------------------------------------------------------------------
* Check '--help attributes' for info on how to pass options via module attributes.
################################################################################
Info:
--------------------------------------------------------------------------------
* Automatically instrumented module io_lib
* Instrumented & loaded module racing_after
################################################################################
Done at 14 Oct 2017 20:56:42 (Exit status: ok)
Summary: 0 errors, 1/1 interleavings explored
@@ -0,0 +1,54 @@
%% When a process under Concuerror receive a message, Concuerror
%% intercepts it and places it in a queue maintained by itself. This
%% is done because Concuerror's scheduler itself communicates with
%% processes via messages and this interception keeps channels of
%% communication clean.
%% When a process under Concuerror is about to execute a receive
%% statement, the instrumentation inspects the patterns, the existence
%% of an after clause and the messages in the queue. If a matching
%% message is found it's "sent" again, for real, so that the actual
%% receive statement can be executed. If no intercepted message
%% matches but an after statement can be executed, no message needs to
%% be placed in the mailbox.
%% In either case, the process used to notify Concuerror's scheduler
%% *before* executing the actual receive statement. This opened up the
%% possibility that the scheduler would send a message of its own
%% asking for the next event, before the receive statement was really
%% executed.
%% This was fine and well if a message could be received (the message
%% would already be in the queue so it would be received), but if the
%% process was supposed to execute the after clause, it could
%% 'accidentally' receive concuerror's scheduler message.
%% The process might crash. Or it might ignore the message and reach
%% the point before the next operation for which it would have to
%% notify the scheduler. It would then wait forever for scheduler's
%% prompt.
%% The scheduler would crash, blaming the process for not responding
%% within reasonable time to its request for a next event, or wait
%% forever.
%% The fix is to simply notify the scheduler after the receive
%% statement has been completed.
-module(racing_after).
-export([test/0]).
-export([scenarios/0]).
scenarios() ->
[{test, inf, dpor}].
test() ->
cleanup_mailbox().
cleanup_mailbox() ->
receive
_ -> cleanup_mailbox()
after
0 -> ok
end.

0 comments on commit c1c641e

Please sign in to comment.