File 1131-erts-Add-BIFs-processes_iterator-0-and-processes_nex.patch of Package erlang
From 69f788451830e796a6843db6be0de0b0950dd952 Mon Sep 17 00:00:00 2001
From: lucioleKi <isabell@erlang.org>
Date: Thu, 28 Nov 2024 12:08:23 +0100
Subject: [PATCH] erts: Add BIFs `processes_iterator/0` and `processes_next/1`
This PR adds 2 BIFs to the `erlang` module.
`processes_iterator/0` returns a process iterator that can be used to
iterate through the process table.
`process_next/1` takes in a process iterator and returns a 2-tuple,
consisting of one process identifier and a new process iterator. If the
process iterator runs out of processes in the process table, `none`
will be returned.
By using these 2 BIFs instead of `processes/0`, one can avoid creating
a potentially huge list of the pids for all existing processes, at the
cost of less consistency guarantees. Process identifiers returned from
consecutive calls of `process_next/1` may not be a consistent snapshot
of all elements existing in the table during any of the calls. The
process identifier of a process that is alive before
`processes_iterator/0` is called and continues to be alive until
`processes_next/1` returns `none` is guaranteed to be part of the
result returned from one of the calls to `processes_next/1`.
---
erts/emulator/beam/bif.c | 18 +++++++
erts/emulator/beam/bif.tab | 1 +
erts/emulator/beam/erl_ptab.c | 66 ++++++++++++++++++++++++
erts/emulator/beam/erl_ptab.h | 3 ++
erts/emulator/test/exception_SUITE.erl | 4 ++
erts/emulator/test/process_SUITE.erl | 40 +++++++++++++--
erts/preloaded/ebin/erlang.beam | Bin 40020 -> 40368 bytes
erts/preloaded/ebin/erts_internal.beam | Bin 10000 -> 10052 bytes
erts/preloaded/src/erlang.erl | 68 +++++++++++++++++++++++++
erts/preloaded/src/erts_internal.erl | 6 +++
lib/kernel/src/erl_erts_errors.erl | 2 +
lib/tools/emacs/erlang.el | 2 +
12 files changed, 207 insertions(+), 3 deletions(-)
diff --git a/erts/emulator/beam/bif.c b/erts/emulator/beam/bif.c
index a32186da8a..5709df04b2 100644
--- a/erts/emulator/beam/bif.c
+++ b/erts/emulator/beam/bif.c
@@ -3825,6 +3825,24 @@ BIF_RETTYPE processes_0(BIF_ALIST_0)
return erts_ptab_list(BIF_P, &erts_proc);
}
+/**********************************************************************/
+/*
+ * The erts_internal:processes_next/1 BIF.
+ */
+
+BIF_RETTYPE erts_internal_processes_next_1(BIF_ALIST_1)
+{
+ Eterm res;
+ if (is_not_small(BIF_ARG_1)) {
+ BIF_ERROR(BIF_P, BADARG);
+ }
+ res = erts_ptab_processes_next(BIF_P, &erts_proc, unsigned_val(BIF_ARG_1));
+ if (is_non_value(res)) {
+ BIF_ERROR(BIF_P, BADARG);
+ }
+ BIF_RET(res);
+}
+
/**********************************************************************/
/*
* The erlang:ports/0 BIF.
diff --git a/erts/emulator/beam/bif.tab b/erts/emulator/beam/bif.tab
index c7aa2bff1c..45c59fe9ac 100644
--- a/erts/emulator/beam/bif.tab
+++ b/erts/emulator/beam/bif.tab
@@ -807,3 +807,4 @@ bif erts_trace_cleaner:send_trace_clean_signal/1
#
bif erts_internal:system_monitor/1
bif erts_internal:system_monitor/3
+bif erts_internal:processes_next/1
diff --git a/erts/emulator/beam/erl_ptab.c b/erts/emulator/beam/erl_ptab.c
index 3740886091..aec2938b61 100644
--- a/erts/emulator/beam/erl_ptab.c
+++ b/erts/emulator/beam/erl_ptab.c
@@ -1465,6 +1465,72 @@ ptab_pix2el(ErtsPTab *ptab, int ix)
return ptab_el;
}
+#define ERTS_PTAB_REDS_MULTIPLIER 25
+
+Eterm
+erts_ptab_processes_next(Process *c_p, ErtsPTab *ptab, Uint first)
+{
+ Uint i;
+ int scanned;
+ Sint limit;
+ Uint need;
+ Eterm res;
+ Eterm* hp;
+ Eterm *hp_end;
+
+ int max_pids = MAX(ERTS_BIF_REDS_LEFT(c_p), 1);
+ int num_pids = 0;
+ int n = max_pids * ERTS_PTAB_REDS_MULTIPLIER;
+ limit = MIN(ptab->r.o.max, first+n);
+
+ if (first == 0) {
+ /*
+ * Ensure no reorder of memory operations made before the first read in
+ * the table with reads in the table.
+ */
+ ETHR_MEMBAR(ETHR_LoadLoad|ETHR_StoreLoad);
+ } else if (first == limit) {
+ /*
+ * Ensure no reorder of memory operations made after the last read in
+ * the table with reads in the table.
+ */
+ ETHR_MEMBAR(ETHR_LoadLoad|ETHR_LoadStore);
+ return am_none;
+ } else if (first > limit) {
+ return THE_NON_VALUE;
+ }
+
+ need = n * 2;
+ hp = HAlloc(c_p, need); /* we need two heap words for each id */
+ hp_end = hp + need;
+ res = make_list(hp);
+
+ for (i = first; i < limit && num_pids < max_pids; i++) {
+ ErtsPTabElementCommon *el = ptab_pix2el(ptab, i);
+ if (el) {
+ hp[0] = el->id;
+ hp[1] = make_list(hp+2);
+ hp += 2;
+ num_pids++;
+ }
+ }
+
+ if (num_pids == 0) {
+ res = NIL;
+ } else {
+ hp[-1] = NIL;
+ }
+
+ scanned = (i - first) / ERTS_PTAB_REDS_MULTIPLIER + 1;
+
+ res = TUPLE2(hp, make_small(i), res);
+ HRelease(c_p, hp_end, hp);
+
+ BUMP_REDS(c_p, scanned);
+
+ return res;
+}
+
Eterm
erts_debug_ptab_list(Process *c_p, ErtsPTab *ptab)
{
diff --git a/erts/emulator/beam/erl_ptab.h b/erts/emulator/beam/erl_ptab.h
index 8753ae9365..a53475152e 100644
--- a/erts/emulator/beam/erl_ptab.h
+++ b/erts/emulator/beam/erl_ptab.h
@@ -474,6 +474,9 @@ ERTS_GLB_INLINE int erts_lc_ptab_is_rwlocked(ErtsPTab *ptab)
BIF_RETTYPE erts_ptab_list(struct process *c_p, ErtsPTab *ptab);
+BIF_RETTYPE erts_ptab_processes_next(struct process *c_p, ErtsPTab *ptab,
+ Uint first);
+
#endif
#if defined(ERTS_PTAB_WANT_DEBUG_FUNCS__) && !defined(ERTS_PTAB_DEBUG_FUNCS__)
diff --git a/erts/emulator/test/exception_SUITE.erl b/erts/emulator/test/exception_SUITE.erl
index e632a5ac98..d72cc86eb0 100644
--- a/erts/emulator/test/exception_SUITE.erl
+++ b/erts/emulator/test/exception_SUITE.erl
@@ -1108,6 +1108,10 @@ error_info(_Config) ->
{process_display, [ExternalPid, whatever]},
{process_display, [DeadProcess, backtrace]},
+ {processes_next, [{a, []}]},
+ {processes_next, [{-1, []}]},
+ {processes_next, [a]},
+
{process_flag, [trap_exit, some_value]},
{process_flag, [bad_flag, some_value]},
diff --git a/erts/emulator/test/process_SUITE.erl b/erts/emulator/test/process_SUITE.erl
index 9680eb5836..becb2c983d 100644
--- a/erts/emulator/test/process_SUITE.erl
+++ b/erts/emulator/test/process_SUITE.erl
@@ -103,7 +103,8 @@
demonitor_aliasmonitor/1,
down_aliasmonitor/1,
monitor_tag/1,
- no_pid_wrap/1]).
+ no_pid_wrap/1,
+ processes_iter/1]).
-export([prio_server/2, prio_client/2, init/1, handle_event/2]).
@@ -163,7 +164,8 @@ groups() ->
processes_small_tab, processes_this_tab,
processes_last_call_trap, processes_apply_trap,
processes_gc_trap, processes_term_proc_list,
- processes_send_infant]},
+ processes_send_infant,
+ processes_iter]},
{process_info_bif, [],
[t_process_info, process_info_messages,
process_info_other, process_info_other_msg,
@@ -2513,7 +2515,14 @@ processes_bif_test() ->
processes()
end,
+ IterProcesses =
+ fun () ->
+ erts_debug:set_internal_state(reds_left, WantReds),
+ iter_all_processes()
+ end,
+
ok = do_processes_bif_test(WantReds, WillTrap, Processes),
+ ok = do_processes_bif_test(WantReds, false, IterProcesses()),
case WillTrap of
false ->
@@ -2550,7 +2559,19 @@ processes_bif_test() ->
undefined -> ok;
Comment -> {comment, Comment}
end.
-
+
+iter_all_processes() ->
+ Iter = erlang:processes_iterator(),
+ iter_all_processes(Iter).
+
+iter_all_processes(Iter0) ->
+ case erlang:processes_next(Iter0) of
+ {Pid, Iter} ->
+ [Pid|iter_all_processes(Iter)];
+ none ->
+ none
+ end.
+
do_processes_bif_test(WantReds, DieTest, Processes) ->
Tester = self(),
SpawnProcesses = fun (Prio) ->
@@ -4199,6 +4220,19 @@ processes_term_proc_list(Config) when is_list(Config) ->
ok.
+processes_iter(Config) when is_list(Config) ->
+ ProcessLimit = erlang:system_info(process_limit),
+ {'EXIT',{badarg,_}} = catch erts_internal:processes_next(ProcessLimit + 1),
+ {'EXIT',{badarg,_}} = catch erts_internal:processes_next(-1),
+ {'EXIT',{badarg,_}} = catch erts_internal:processes_next(1 bsl 32),
+ {'EXIT',{badarg,_}} = catch erts_internal:processes_next(1 bsl 64),
+ {'EXIT',{badarg,_}} = catch erts_internal:processes_next(abc),
+
+ none = erts_internal:processes_next(ProcessLimit),
+
+ ok.
+
+
%% OTP-18322: Send msg to spawning process pid returned from processes/0
processes_send_infant(_Config) ->
case erlang:system_info(schedulers_online) of
diff --git a/erts/preloaded/src/erlang.erl b/erts/preloaded/src/erlang.erl
index 3c950c0a2f..304f6bdcc7 100644
--- a/erts/preloaded/src/erlang.erl
+++ b/erts/preloaded/src/erlang.erl
@@ -265,6 +265,7 @@ A timeout value that can be passed to a
-export_type([message_queue_data/0]).
-export_type([monitor_option/0]).
-export_type([stacktrace/0]).
+-export_type([processes_iter_ref/0]).
-type stacktrace_extrainfo() ::
{line, pos_integer()} |
@@ -465,6 +466,7 @@ A list of binaries. This datatype is useful to use together with
-export([time_offset/0, time_offset/1, timestamp/0]).
-export([process_display/2]).
-export([process_flag/3, process_info/1, processes/0, purge_module/1]).
+-export([processes_iterator/0, processes_next/1]).
-export([put/2, raise/3, read_timer/1, read_timer/2, ref_to_list/1, register/2]).
-export([send_after/3, send_after/4, start_timer/3, start_timer/4]).
-export([registered/0, resume_process/1, round/1, self/0]).
@@ -5219,6 +5221,72 @@ Example:
processes() ->
erlang:nif_error(undefined).
+%% The process iterator is a 2-tuple, consisting of an index to the process
+%% table and a list of process identifiers that existed when the last scan of
+%% the process table took place. The index is the starting place for the next
+%% scan of the process table.
+-opaque processes_iter_ref() :: {integer(), [pid()]}.
+
+%% processes_iterator/0
+-doc """
+Returns a processes iterator that can be used in
+[`processes_next/1`](`processes_next/1`).
+""".
+-doc #{ group => processes, since => <<"OTP 28.0">> }.
+-spec processes_iterator() -> processes_iter_ref().
+processes_iterator() ->
+ {0, []}.
+
+%% processes_next/1
+-doc """
+Returns a 2-tuple, consisting of one process identifier and a new processes
+iterator. If the process iterator has run out of processes in the process table,
+`none` will be returned.
+
+The two major benefits of using the `processes_iterator/0`/`processes_next/1`
+BIFs instead of using the `processes/0` BIF are that they scale better since
+no locking is needed, and you do not risk getting a huge list allocated on the
+heap if there are a huge amount of processes alive in the system.
+
+Example:
+
+```erlang
+> I0 = erlang:processes_iterator(), ok.
+ok
+> {Pid1, I1} = erlang:processes_next(I0), Pid1.
+<0.0.0>,
+> {Pid2, I2} = erlang:processes_next(I1), Pid2.
+<0.1.0>
+```
+
+> #### Note {: .info }
+>
+> This BIF has less consistency guarantee than [`processes/0`](`processes/0`).
+> Process identifiers returned from consecutive calls of this BIF may not be a
+> consistent snapshot of all elements existing in the table during any of the
+> calls. The process identifier of a process that is alive before
+> `processes_iterator/0` is called and continues to be alive until
+> `processes_next/1` returns `none` is guaranteed to be part of the result
+> returned from one of the calls to `processes_next/1`.
+""".
+-doc #{ group => processes, since => <<"OTP 28.0">> }.
+-spec processes_next(Iter) -> {Pid, NewIter} | 'none' when
+ Iter :: processes_iter_ref(),
+ NewIter :: processes_iter_ref(),
+ Pid :: pid().
+processes_next({IterRef, [Pid|Pids]}) ->
+ {Pid, {IterRef, Pids}};
+processes_next({IterRef0, []}=Arg) ->
+ try erts_internal:processes_next(IterRef0) of
+ none -> none;
+ {IterRef, [Pid|Pids]} -> {Pid, {IterRef, Pids}};
+ {IterRef, []} -> processes_next({IterRef, []})
+ catch error:badarg ->
+ badarg_with_info([Arg])
+ end;
+processes_next(Arg) ->
+ badarg_with_info([Arg]).
+
%% purge_module/1
-doc """
Removes old code for `Module`. Before this BIF is used, `check_process_code/2`
diff --git a/erts/preloaded/src/erts_internal.erl b/erts/preloaded/src/erts_internal.erl
index e0aaf5ee6a..07a3c4b5cc 100644
--- a/erts/preloaded/src/erts_internal.erl
+++ b/erts/preloaded/src/erts_internal.erl
@@ -129,6 +129,8 @@
-export([system_monitor/1, system_monitor/3]).
+-export([processes_next/1]).
+
%%
%% Await result of send to port
%%
@@ -1166,3 +1168,7 @@ system_monitor(_Session) ->
Return :: undefined | ok | {pid(), Options}.
system_monitor(_Session, _MonitorPid, _Options) ->
erlang:nif_error(undefined).
+
+-spec processes_next(integer()) -> {integer(), [pid()]} | 'none'.
+processes_next(_IterRef) ->
+ erlang:nif_error(undefined).
diff --git a/lib/kernel/src/erl_erts_errors.erl b/lib/kernel/src/erl_erts_errors.erl
index 469907dac3..33ac547d91 100644
--- a/lib/kernel/src/erl_erts_errors.erl
+++ b/lib/kernel/src/erl_erts_errors.erl
@@ -733,6 +733,8 @@ format_erlang_error(process_display, [Pid,_], Cause) ->
_ ->
[must_be_local_pid(Pid, dead_process)]
end;
+format_erlang_error(processes_next, [_], _Cause) ->
+ [~"invalid processes iterator"];
format_erlang_error(process_flag, [_,_], Cause) ->
case Cause of
badopt ->
diff --git a/lib/tools/emacs/erlang.el b/lib/tools/emacs/erlang.el
index f582c198ad..692ccf961a 100644
--- a/lib/tools/emacs/erlang.el
+++ b/lib/tools/emacs/erlang.el
@@ -993,6 +993,8 @@ resulting regexp is surrounded by \\_< and \\_>."
"posixtime_to_universaltime"
"prepare_loading"
"process_display"
+ "processes_iterator"
+ "processes_next"
"raise"
"read_timer"
"resume_process"
--
2.43.0