Skip to content

Commit 31df515

Browse files
Merge pull request #11463 from rickard-green/rickard/nosuspend-task-fix/GH-11052/OTP-20135
Make sure nosuspend command signals to ports are handled properly
2 parents 4d636c0 + 4b5f9b7 commit 31df515

10 files changed

Lines changed: 219 additions & 22 deletions

File tree

erts/emulator/beam/dist.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1033,7 +1033,8 @@ int erts_do_net_exits(DistEntry *dep, Eterm reason)
10331033
ERTS_LC_ASSERT(erts_lc_is_port_locked(erts_port_lookup_raw(dep->cid)));
10341034

10351035
if (erts_port_task_is_scheduled(&dep->dist_cmd))
1036-
erts_port_task_abort(&dep->dist_cmd);
1036+
erts_port_task_abort(erts_port_lookup_raw(dep->cid),
1037+
&dep->dist_cmd);
10371038
}
10381039
else {
10391040
ASSERT(is_internal_pid(dep->cid));

erts/emulator/beam/erl_hl_timer.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3070,7 +3070,7 @@ erts_cancel_port_timer(Port *c_prt)
30703070
if (tval == ERTS_PTMR_TIMEDOUT) {
30713071
while (!erts_port_task_is_scheduled(&c_prt->timeout_task))
30723072
erts_thr_yield();
3073-
erts_port_task_abort(&c_prt->timeout_task);
3073+
erts_port_task_abort(c_prt, &c_prt->timeout_task);
30743074
erts_atomic_set_nob(&c_prt->common.timer, ERTS_PTMR_NONE);
30753075
return;
30763076
}

erts/emulator/beam/erl_port.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -873,6 +873,7 @@ enum {
873873
#define ERTS_P2P_SIG_DATA_FLG_BROKEN_LINK ERTS_P2P_SIG_DATA_FLG(5)
874874
#define ERTS_P2P_SIG_DATA_FLG_SCHED ERTS_P2P_SIG_DATA_FLG(6)
875875
#define ERTS_P2P_SIG_DATA_FLG_ASYNC ERTS_P2P_SIG_DATA_FLG(7)
876+
#define ERTS_P2P_SIG_DATA_FLG_ASYNC_NOSUSPEND ERTS_P2P_SIG_DATA_FLG(8)
876877

877878
struct ErtsProc2PortSigData_ {
878879
int flags;

erts/emulator/beam/erl_port_task.c

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,6 +1008,8 @@ enqueue_task(Port *pp,
10081008
if (ns_pthlp) {
10091009
ns_pthlp->u.next = pp->sched.taskq.local.busy.nosuspend;
10101010
pp->sched.taskq.local.busy.nosuspend = ns_pthlp;
1011+
erts_atomic32_read_bor_nob(&pp->sched.flags,
1012+
ERTS_PTS_FLG_HAVE_NS_TASKS);
10111013
}
10121014
if (pp->sched.taskq.in.last) {
10131015
ASSERT(pp->sched.taskq.in.first);
@@ -1305,7 +1307,7 @@ erts_port_task_tmp_handle_detach(ErtsPortTaskHandle *pthp)
13051307
*/
13061308

13071309
int
1308-
erts_port_task_abort(ErtsPortTaskHandle *pthp)
1310+
erts_port_task_abort(Port *pp, ErtsPortTaskHandle *pthp)
13091311
{
13101312
int res;
13111313
ErtsPortTask *ptp;
@@ -1333,22 +1335,33 @@ erts_port_task_abort(ErtsPortTaskHandle *pthp)
13331335
res = - 1; /* Task already aborted, executing, or executed */
13341336
else {
13351337
reset_port_task_handle(pthp);
1338+
switch (ptp->type) {
1339+
case ERTS_PORT_TASK_PROC_SIG: {
1340+
int abort_type = ((ptp->u.alive.flags
1341+
& (ERTS_PT_FLG_NOSUSPEND
1342+
| ERTS_PT_FLG_ASYNC_NOSUSPEND))
1343+
? ERTS_PROC2PORT_SIG_ABORT_NOSUSPEND
1344+
: ERTS_PROC2PORT_SIG_ABORT);
1345+
ASSERT(pp);
1346+
abort_signal_task(pp, abort_type, ptp->type, &ptp->u.alive.td,
1347+
pp->sched.taskq.bpq != NULL);
1348+
break;
1349+
}
1350+
case ERTS_PORT_TASK_INPUT:
1351+
case ERTS_PORT_TASK_OUTPUT:
13361352
#if ERTS_POLL_USE_SCHEDULER_POLLING
1337-
if (erts_sched_poll_enabled()) {
1338-
switch (ptp->type) {
1339-
case ERTS_PORT_TASK_INPUT:
1340-
case ERTS_PORT_TASK_OUTPUT:
1353+
if (erts_sched_poll_enabled()) {
13411354
if (ptp->u.alive.td.io.is_scheduler_event) {
13421355
ASSERT(erts_atomic_read_nob(
13431356
&erts_port_task_outstanding_io_tasks) > 0);
13441357
erts_atomic_dec_relb(&erts_port_task_outstanding_io_tasks);
13451358
}
1346-
break;
1347-
default:
1348-
break;
13491359
}
1350-
}
13511360
#endif
1361+
break;
1362+
default:
1363+
break;
1364+
}
13521365
res = 0;
13531366
}
13541367
}
@@ -1376,7 +1389,6 @@ erts_port_task_abort_nosuspend_tasks(Port *pp)
13761389
ErtsPortTaskHandle *saved_pthp;
13771390
#endif
13781391
ErtsPortTaskType type;
1379-
ErtsPortTaskTypeData td;
13801392
ErtsPortTaskHandle *pthp;
13811393
ErtsPortTask *ptp;
13821394
ErtsPortTaskHandleList *pthlp;
@@ -1420,13 +1432,14 @@ erts_port_task_abort_nosuspend_tasks(Port *pp)
14201432
reset_port_task_handle(pthp);
14211433

14221434
type = ptp->type;
1423-
td = ptp->u.alive.td;
1435+
1436+
abort_nosuspend_task(pp, type, &ptp->u.alive.td,
1437+
pp->sched.taskq.bpq != NULL);
14241438

14251439
if (dhndl != ERTS_THR_PRGR_DHANDLE_MANAGED)
14261440
erts_thr_progress_unmanaged_continue(dhndl);
14271441
schedule_port_task_handle_list_free(pthlp);
14281442

1429-
abort_nosuspend_task(pp, type, &td, pp->sched.taskq.bpq != NULL);
14301443
}
14311444
}
14321445

@@ -1527,8 +1540,6 @@ erts_port_task_schedule(Eterm id,
15271540
}
15281541

15291542
add_flags = ERTS_PTS_FLG_HAVE_TASKS;
1530-
if (ns_pthlp)
1531-
add_flags |= ERTS_PTS_FLG_HAVE_NS_TASKS;
15321543

15331544
prof_runnable_ports = erts_system_profile_flags.runnable_ports;
15341545
if (prof_runnable_ports)

erts/emulator/beam/erl_port_task.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ typedef erts_atomic_t ErtsPortTaskHandle;
5757
#define ERTS_PT_FLG_NOSUSPEND (1 << 2)
5858
#define ERTS_PT_FLG_REF (1 << 3)
5959
#define ERTS_PT_FLG_BAD_OUTPUT (1 << 4)
60+
#define ERTS_PT_FLG_ASYNC_NOSUSPEND (1 << 5)
6061

6162
typedef enum {
6263
ERTS_PORT_TASK_INPUT = 0,
@@ -256,7 +257,7 @@ void erts_port_task_init(void);
256257
void erts_port_task_pre_alloc_init_thread(void);
257258

258259
void erts_port_task_tmp_handle_detach(ErtsPortTaskHandle *);
259-
int erts_port_task_abort(ErtsPortTaskHandle *);
260+
int erts_port_task_abort(Port *, ErtsPortTaskHandle *);
260261

261262
void erts_port_task_abort_nosuspend_tasks(Port *);
262263

erts/emulator/beam/io.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2112,6 +2112,8 @@ erts_port_output(Process *c_p,
21122112
ASSERT(esdp);
21132113
ns_pthp = &esdp->nosuspend_port_task_handle;
21142114
sigdp->flags &= ~ERTS_P2P_SIG_DATA_FLG_NOSUSPEND;
2115+
sigdp->flags |= ERTS_P2P_SIG_DATA_FLG_ASYNC_NOSUSPEND;
2116+
task_flags = ERTS_PT_FLG_WAIT_BUSY|ERTS_PT_FLG_ASYNC_NOSUSPEND;
21152117
}
21162118
else if (flags & ERTS_P2P_SIG_DATA_FLG_NOSUSPEND)
21172119
task_flags = ERTS_PT_FLG_NOSUSPEND;
@@ -2143,7 +2145,7 @@ erts_port_output(Process *c_p,
21432145
if (!async_nosuspend)
21442146
return ERTS_PORT_OP_BUSY_SCHEDULED;
21452147
else {
2146-
if (erts_port_task_abort(ns_pthp) == 0)
2148+
if (erts_port_task_abort(prt, ns_pthp) == 0)
21472149
return ERTS_PORT_OP_BUSY;
21482150
else
21492151
erts_port_task_tmp_handle_detach(ns_pthp);

erts/emulator/sys/common/erl_check_io.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -653,7 +653,7 @@ static ERTS_INLINE void
653653
abort_task(Eterm id, ErtsPortTaskHandle *pthp, EventStateType type)
654654
{
655655
if (is_not_nil(id) && erts_port_task_is_scheduled(pthp)) {
656-
erts_port_task_abort(pthp);
656+
erts_port_task_abort(NULL, pthp);
657657
ASSERT(erts_is_port_alive(id));
658658
}
659659
}

erts/emulator/test/busy_port_SUITE.erl

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@
3030
scheduling_delay_busy/1,
3131
scheduling_delay_busy_nosuspend/1,
3232
scheduling_busy_link/1,
33-
busy_with_signals/1]).
33+
busy_with_signals/1,
34+
async_par_nosuspend_abort_cleanup/1,
35+
async_nopar_nosuspend_abort_cleanup/1,
36+
sync_par_nosuspend_abort_cleanup/1,
37+
sync_nopar_nosuspend_abort_cleanup/1]).
3438

3539
-include_lib("common_test/include/ct.hrl").
3640

@@ -46,7 +50,11 @@ all() ->
4650
no_trap_exit, no_trap_exit_unlinked, trap_exit,
4751
multiple_writers, hard_busy_driver, soft_busy_driver,
4852
scheduling_delay_busy,scheduling_delay_busy_nosuspend,
49-
scheduling_busy_link, busy_with_signals].
53+
scheduling_busy_link, busy_with_signals,
54+
async_par_nosuspend_abort_cleanup,
55+
async_nopar_nosuspend_abort_cleanup,
56+
sync_par_nosuspend_abort_cleanup,
57+
sync_nopar_nosuspend_abort_cleanup].
5058

5159
init_per_testcase(_Case, Config) when is_list(Config) ->
5260
Killer = spawn(fun() -> killer_loop([]) end),
@@ -846,6 +854,69 @@ flood_with_exit_signals(Pid, N) ->
846854
exit(Pid, pling),
847855
flood_with_exit_signals(Pid, N-1).
848856

857+
async_par_nosuspend_abort_cleanup(Config) when is_list(Config) ->
858+
nosuspend_abort_cleanup_tests(Config, false, true).
859+
860+
async_nopar_nosuspend_abort_cleanup(Config) when is_list(Config) ->
861+
nosuspend_abort_cleanup_tests(Config, false, false).
862+
863+
sync_par_nosuspend_abort_cleanup(Config) when is_list(Config) ->
864+
nosuspend_abort_cleanup_tests(Config, true, true).
865+
866+
sync_nopar_nosuspend_abort_cleanup(Config) when is_list(Config) ->
867+
nosuspend_abort_cleanup_tests(Config, true, false).
868+
869+
nosuspend_abort_cleanup_tests(Config, Sync, Parallelism) ->
870+
Master = self(),
871+
process_flag(priority,high),
872+
Drv = "nosuspend_test_drv",
873+
DataDir = proplists:get_value(data_dir, Config),
874+
erl_ddll:start(),
875+
case erl_ddll:load_driver(DataDir, Drv) of
876+
ok ->
877+
ok;
878+
{error, Error} ->
879+
ct:fail(erl_ddll:format_error(Error))
880+
end,
881+
Port = open_port({spawn_driver, Drv},
882+
[{busy_limits_msgq, {1024, 2048}},
883+
{parallelism, Parallelism}]),
884+
NoSuspFun = if Sync ->
885+
fun () ->
886+
port_command(Port, <<0:(16*8*1024)>>, [nosuspend])
887+
end;
888+
true ->
889+
fun () ->
890+
erlang:send(Port,
891+
{Master, {command, <<0:(16*8*1024)>>}},
892+
[nosuspend])
893+
end
894+
end,
895+
NoSuspers = lists:map(fun (_) -> spawn_link(NoSuspFun) end,
896+
lists:seq(1, 10000)),
897+
_= port_control(Port, $B, <<>>),
898+
receive after 1000 -> ok end,
899+
_= port_control(Port, $N, <<>>),
900+
SyncCmd = spawn_link(fun () ->
901+
port_command(Port, <<"should not block for long">>),
902+
Master ! {self(), done}
903+
end),
904+
receive
905+
{SyncCmd, done} -> ok
906+
after
907+
10000 -> ct:fail(port_command_blocked)
908+
end,
909+
lists:foreach(fun (P) ->
910+
unlink(P),
911+
exit(P,kill),
912+
false = is_process_alive(P)
913+
end,
914+
[SyncCmd | NoSuspers]),
915+
true = erlang:port_close(Port),
916+
ok = erl_ddll:unload_driver(Drv),
917+
ok = erl_ddll:stop(),
918+
ok.
919+
849920
%%% Utilities.
850921

851922
pal(_F,_A) -> ok.

erts/emulator/test/busy_port_SUITE_data/Makefile.src

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
# %CopyrightEnd%
2121
#
2222

23-
all: busy_drv@dll@ hard_busy_drv@dll@ soft_busy_drv@dll@ scheduling_drv@dll@
23+
all: busy_drv@dll@ hard_busy_drv@dll@ soft_busy_drv@dll@ scheduling_drv@dll@ nosuspend_test_drv@dll@
2424

2525
@SHLIB_RULES@
2626

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* %CopyrightBegin%
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*
6+
* Copyright Ericsson AB 2026. All Rights Reserved.
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*
20+
* %CopyrightEnd%
21+
*/
22+
23+
#include <errno.h>
24+
#include <stdbool.h>
25+
#include "erl_driver.h"
26+
27+
static void stop(ErlDrvData drv_data);
28+
static ErlDrvData start(ErlDrvPort port, char *command);
29+
static void output(ErlDrvData drv_data, char *buf, ErlDrvSizeT len);
30+
static ErlDrvSSizeT control(ErlDrvData drv_data, unsigned int command, char *buf,
31+
ErlDrvSizeT len, char **rbuf, ErlDrvSizeT rlen);
32+
33+
static ErlDrvEntry nosuspend_test_drv_entry = {
34+
NULL /* init */,
35+
start,
36+
stop,
37+
output,
38+
NULL /* ready_input */,
39+
NULL /* ready_output */,
40+
"nosuspend_test_drv",
41+
NULL /* finish */,
42+
NULL /* handle */,
43+
control,
44+
NULL /* timeout */,
45+
NULL /* outputv */,
46+
NULL /* ready_async */,
47+
NULL /* flush */,
48+
NULL /* call */,
49+
NULL /* event */,
50+
ERL_DRV_EXTENDED_MARKER,
51+
ERL_DRV_EXTENDED_MAJOR_VERSION,
52+
ERL_DRV_EXTENDED_MINOR_VERSION,
53+
ERL_DRV_FLAG_USE_PORT_LOCKING,
54+
NULL /* handle2 */,
55+
NULL /* handle_monitor */,
56+
NULL /* stop_select */
57+
};
58+
59+
typedef struct {
60+
ErlDrvPort port;
61+
bool is_busy;
62+
} PortState;
63+
64+
DRIVER_INIT(nosuspend_test_drv)
65+
{
66+
return &nosuspend_test_drv_entry;
67+
}
68+
69+
static void stop(ErlDrvData drv_data)
70+
{
71+
driver_free(drv_data);
72+
}
73+
74+
static ErlDrvData start(ErlDrvPort port, char *command)
75+
{
76+
PortState *state = driver_alloc(sizeof(PortState));
77+
if (!state)
78+
return ERL_DRV_ERROR_GENERAL;
79+
state->port = port;
80+
state->is_busy = false;
81+
return (ErlDrvData) state;
82+
}
83+
84+
static void output(ErlDrvData drv_data, char *buf, ErlDrvSizeT len)
85+
{
86+
PortState *state = (PortState *) drv_data;
87+
if (state->is_busy) {
88+
driver_failure_atom(state->port, "got_data_when_busy");
89+
}
90+
}
91+
92+
static ErlDrvSSizeT control(ErlDrvData drv_data, unsigned int command, char *buf,
93+
ErlDrvSizeT len, char **rbuf, ErlDrvSizeT rlen)
94+
{
95+
PortState *state = (PortState *) drv_data;
96+
switch (command) {
97+
case 'B': /* busy */
98+
set_busy_port(state->port, !0);
99+
state->is_busy = true;
100+
break;
101+
case 'N': /* not busy */
102+
set_busy_port(state->port, 0);
103+
state->is_busy = false;
104+
break;
105+
default:
106+
driver_failure_posix(state->port, EINVAL);
107+
break;
108+
}
109+
return 0;
110+
}

0 commit comments

Comments
 (0)