Skip to content

Commit bf262be

Browse files
committed
Merge pull request #455 from fadushin/wait-timeout-infinity-fix
This PR fixes calls to `timer:sleep` with `infinity` as a parameter. Also does range checking on integer values to ensure values are within range (per OTP). This PR also fixes a bug in the handling of messages that have come in to a process while it is waiting for a timeout. Previously, a process would resume prematurely if it received a message in its mailbox and it was waiting with a timeout. As a result of these changes, the implementation of `timer:sleep` is greatly simplified. These changes are made under both the "Apache 2.0" and the "GNU Lesser General Public License 2.1 or later" license terms (dual license). SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
2 parents 6501b0a + a1dbb26 commit bf262be

File tree

7 files changed

+56
-62
lines changed

7 files changed

+56
-62
lines changed

libs/estdlib/src/timer.erl

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,9 @@
2222

2323
-export([sleep/1]).
2424

25-
-define(MAX_INT, 4294967295).
26-
2725
-spec sleep(non_neg_integer() | infinity) -> ok.
28-
sleep(infinity) ->
29-
receive
30-
'$atomvm_timer_interrupt' ->
31-
{error, unexpected_interrupt}
32-
after ?MAX_INT -> sleep(infinity)
33-
end;
34-
sleep(MSecs) ->
26+
sleep(Timeout) ->
3527
receive
36-
'$atomvm_timer_interrupt' ->
37-
{error, unexpected_interrupt}
38-
after MSecs -> ok
28+
after Timeout ->
29+
ok
3930
end.

src/libAtomVM/opcodesswitch.h

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2145,10 +2145,16 @@ static bool maybe_call_native(Context *ctx, AtomString module_name, AtomString f
21452145
DECODE_COMPACT_TERM(timeout, code, i, next_off)
21462146

21472147
#ifdef IMPL_EXECUTE_LOOP
2148-
if (!term_is_integer(timeout) && UNLIKELY(timeout != INFINITY_ATOM)) {
2148+
avm_int64_t t = 0;
2149+
if (term_is_any_integer(timeout)) {
2150+
t = term_maybe_unbox_int64(timeout);
2151+
if (UNLIKELY(t < 0)) {
2152+
RAISE_ERROR(TIMEOUT_VALUE_ATOM);
2153+
}
2154+
} else if (UNLIKELY(timeout != INFINITY_ATOM)) {
21492155
RAISE_ERROR(TIMEOUT_VALUE_ATOM);
21502156
}
2151-
TRACE("wait_timeout/2, label: %i, timeout: %li\n", label, (long int) term_to_int32(timeout));
2157+
TRACE("wait_timeout/2, label: %i, timeout: %li\n", label, (long int) t);
21522158

21532159
NEXT_INSTRUCTION(next_off);
21542160
//TODO: it looks like x[0] might be used instead of jump_to_on_restore
@@ -2159,10 +2165,10 @@ static bool maybe_call_native(Context *ctx, AtomString module_name, AtomString f
21592165
int needs_to_wait = 0;
21602166
if ((ctx->flags & (WaitingTimeout | WaitingTimeoutExpired)) == 0) {
21612167
if (timeout != INFINITY_ATOM) {
2162-
scheduler_set_timeout(ctx, term_to_int32(timeout));
2168+
scheduler_set_timeout(ctx, t);
21632169
}
21642170
needs_to_wait = 1;
2165-
} else if ((ctx->flags & WaitingTimeout) == 0) {
2171+
} else if ((ctx->flags & WaitingTimeout) != 0) {
21662172
needs_to_wait = 1;
21672173
} else if (!list_is_empty(&ctx->save_queue)) {
21682174
needs_to_wait = 1;

src/libAtomVM/scheduler.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ static void scheduler_timeout_callback(struct TimerWheelItem *it)
139139
scheduler_make_ready(ctx->global, ctx);
140140
}
141141

142-
void scheduler_set_timeout(Context *ctx, uint32_t timeout)
142+
void scheduler_set_timeout(Context *ctx, avm_int64_t timeout)
143143
{
144144
GlobalContext *glb = ctx->global;
145145

src/libAtomVM/scheduler.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ Context *scheduler_next(GlobalContext *global, Context *c);
102102
* @param ctx the context that will be put on sleep
103103
* @param timeout amount of time to be waited in milliseconds.
104104
*/
105-
void scheduler_set_timeout(Context *ctx, uint32_t timeout);
105+
void scheduler_set_timeout(Context *ctx, avm_int64_t timeout);
106106

107107
void scheduler_cancel_timeout(Context *ctx);
108108

src/libAtomVM/timer_wheel.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ extern "C" {
2929
#include <stdint.h>
3030

3131
#include "list.h"
32+
#include "term_typedef.h"
3233

3334
struct TimerWheelItem;
3435
typedef void(timer_wheel_callback_t)(struct TimerWheelItem *);
@@ -84,7 +85,7 @@ static inline void timer_wheel_item_init(struct TimerWheelItem *it, timer_wheel_
8485
it->callback = cb;
8586
}
8687

87-
static inline uint64_t timer_wheel_expiry_to_monotonic(const struct TimerWheel *tw, uint32_t expiry)
88+
static inline uint64_t timer_wheel_expiry_to_monotonic(const struct TimerWheel *tw, avm_int64_t expiry)
8889
{
8990
return tw->monotonic_time + expiry;
9091
}

tests/libs/estdlib/test_timer.erl

Lines changed: 38 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -22,65 +22,61 @@
2222

2323
-export([test/0]).
2424

25+
-include("etest.hrl").
26+
2527
test() ->
2628
ok = test_timer(),
27-
ok = test_timer_interrupt(),
2829
ok = test_timer_loop(),
30+
ok = test_timer_badargs(),
31+
ok = test_infinity(),
2932
ok.
3033

31-
-include("etest.hrl").
32-
3334
test_timer() ->
34-
T0 = erlang:timestamp(),
35+
T0 = erlang:system_time(millisecond),
3536
ok = timer:sleep(101),
36-
T1 = erlang:timestamp(),
37-
ok = etest:assert_true((to_ms(T1) - to_ms(T0)) >= 101),
37+
T1 = erlang:system_time(millisecond),
38+
ok = etest:assert_true((T1 - T0) >= 101),
3839
ok.
3940

40-
test_timer_interrupt() ->
41-
Self = self(),
42-
Pid = spawn(fun() -> do_test_interrupt(Self) end),
43-
receive
44-
ready -> ok
45-
end,
46-
47-
%% this message should not interrupt the timer
48-
Pid ! try_to_interrupt,
49-
50-
Pid ! '$atomvm_timer_interrupt',
51-
?ASSERT_MATCH(pop_mailbox(), ok),
52-
ok.
53-
54-
pop_mailbox() ->
55-
receive
56-
X -> X
57-
end.
58-
59-
do_test_interrupt(Pid) ->
60-
Pid ! ready,
61-
case timer:sleep(infinity) of
62-
{error, unexpected_interrupt} ->
63-
Pid ! ok;
64-
_ ->
65-
Pid ! error
66-
end.
67-
6841
test_timer_loop() ->
6942
Self = self(),
7043
spawn(fun() ->
71-
timer:sleep(220),
72-
Self ! ping
44+
Self ! ready,
45+
timer:sleep(50),
46+
Self ! noise
7347
end),
48+
receive
49+
ready ->
50+
ok
51+
end,
7452
ok = timer_loop(5).
7553

7654
timer_loop(0) ->
77-
ok;
55+
receive
56+
noise ->
57+
ok;
58+
SomethingElse ->
59+
{error, SomethingElse}
60+
end;
7861
timer_loop(I) ->
79-
T0 = erlang:timestamp(),
62+
T0 = erlang:system_time(millisecond),
8063
ok = timer:sleep(101),
81-
T1 = erlang:timestamp(),
82-
ok = etest:assert_true((to_ms(T1) - to_ms(T0)) >= 101),
64+
T1 = erlang:system_time(millisecond),
65+
ok = etest:assert_true((T1 - T0) >= 101),
8366
timer_loop(I - 1).
8467

85-
to_ms({MegaSecs, Secs, MicroSecs}) ->
86-
((MegaSecs * 1000000 + Secs) * 1000 + MicroSecs div 1000).
68+
test_timer_badargs() ->
69+
{'EXIT', {timeout_value, _}} = (catch timer:sleep(-1)),
70+
{'EXIT', {timeout_value, _}} = (catch timer:sleep(not_infinity)),
71+
ok.
72+
73+
test_infinity() ->
74+
Self = self(),
75+
Pid = spawn(fun() ->
76+
Self ! ok,
77+
timer:sleep(infinity)
78+
end),
79+
receive
80+
ok ->
81+
ok = etest:assert_true(erlang:is_process_alive(Pid))
82+
end.

tests/libs/estdlib/tests.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,6 @@ start() ->
3232
test_io_lib,
3333
test_maps,
3434
test_proplists,
35-
% , test_timer TODO: enable it again, once we try a way to make it less flaky
35+
test_timer,
3636
test_supervisor
3737
]).

0 commit comments

Comments
 (0)