diff --git a/CHANGELOG.md b/CHANGELOG.md index ca5c4289a1..22d216bcd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added `erlang:node/1` BIF - Added `erts_internal:cmp_term/2` +- Added support for `process_info/1` and `process_info/2` with list argument ### Changed diff --git a/doc/src/apidocs/libatomvm/data_structures.rst b/doc/src/apidocs/libatomvm/data_structures.rst index cf9c85f55b..2bee3f9fde 100644 --- a/doc/src/apidocs/libatomvm/data_structures.rst +++ b/doc/src/apidocs/libatomvm/data_structures.rst @@ -23,7 +23,7 @@ Data Structures :allow-dot-graphs: .. doxygenstruct:: AVMPackData :allow-dot-graphs: -.. doxygenstruct:: BuiltInAtomRequestSignal +.. doxygenstruct:: ProcessInfoRequestSignal :allow-dot-graphs: .. doxygenstruct:: BuiltInAtomSignal :allow-dot-graphs: diff --git a/src/libAtomVM/context.c b/src/libAtomVM/context.c index 0e35082ad1..a9830480a7 100644 --- a/src/libAtomVM/context.c +++ b/src/libAtomVM/context.c @@ -156,8 +156,8 @@ void context_destroy(Context *ctx) while (signal_message) { switch (signal_message->type) { case ProcessInfoRequestSignal: { - struct BuiltInAtomRequestSignal *request_signal - = CONTAINER_OF(signal_message, struct BuiltInAtomRequestSignal, base); + struct ProcessInfoRequestSignal *request_signal + = CONTAINER_OF(signal_message, struct ProcessInfoRequestSignal, base); context_process_process_info_request_signal(ctx, request_signal, true); break; } @@ -312,7 +312,7 @@ void context_process_kill_signal(Context *ctx, struct TermSignal *signal) context_update_flags(ctx, ~NoFlags, Killed); } -void context_process_process_info_request_signal(Context *ctx, struct BuiltInAtomRequestSignal *signal, bool process_table_locked) +void context_process_process_info_request_signal(Context *ctx, struct ProcessInfoRequestSignal *signal, bool process_table_locked) { Context *target; if (process_table_locked) { @@ -320,28 +320,85 @@ void context_process_process_info_request_signal(Context *ctx, struct BuiltInAto } else { target = globalcontext_get_process_lock(ctx->global, signal->sender_pid); } - if (LIKELY(target)) { + + if (UNLIKELY(!target)) { + return; + } + + if (signal->mode == PROCESS_INFO_SINGLE) { + term atom = signal->atoms[0]; size_t term_size; - if (context_get_process_info(ctx, NULL, &term_size, signal->atom, NULL)) { - Heap heap; - if (UNLIKELY(memory_init_heap(&heap, term_size) != MEMORY_GC_OK)) { - mailbox_send_immediate_signal(target, TrapExceptionSignal, OUT_OF_MEMORY_ATOM); - } else { - term ret; - if (context_get_process_info(ctx, &ret, NULL, signal->atom, &heap)) { - mailbox_send_term_signal(target, TrapAnswerSignal, ret); - } else { - mailbox_send_immediate_signal(target, TrapExceptionSignal, ret); - } - memory_destroy_heap(&heap, ctx->global); + if (!context_get_process_info(ctx, NULL, &term_size, atom, NULL)) { + mailbox_send_immediate_signal(target, TrapExceptionSignal, BADARG_ATOM); + goto done; + } + + Heap heap; + if (UNLIKELY(memory_init_heap(&heap, term_size) != MEMORY_GC_OK)) { + mailbox_send_immediate_signal(target, TrapExceptionSignal, OUT_OF_MEMORY_ATOM); + goto done; + } + + term ret; + if (context_get_process_info(ctx, &ret, NULL, atom, &heap)) { + // return [] when unregistered (BEAM backward compatibility) + if (atom == REGISTERED_NAME_ATOM && term_is_tuple(ret) && term_is_nil(term_get_tuple_element(ret, 1))) { + ret = term_nil(); } + mailbox_send_term_signal(target, TrapAnswerSignal, ret); } else { - mailbox_send_immediate_signal(target, TrapExceptionSignal, BADARG_ATOM); + mailbox_send_immediate_signal(target, TrapExceptionSignal, ret); + } + memory_destroy_heap(&heap, ctx->global); + } else { + size_t total_size = 0; + for (size_t i = 0; i < signal->len; i++) { + size_t item_size; + if (UNLIKELY(!context_get_process_info(ctx, NULL, &item_size, signal->atoms[i], NULL))) { + mailbox_send_immediate_signal(target, TrapExceptionSignal, BADARG_ATOM); + goto done; + } + total_size += item_size + CONS_SIZE; + } + + if (signal->len == 0) { + mailbox_send_term_signal(target, TrapAnswerSignal, term_nil()); + goto done; } - if (!process_table_locked) { - globalcontext_get_process_unlock(ctx->global, target); + + Heap heap; + if (UNLIKELY(memory_init_heap(&heap, total_size) != MEMORY_GC_OK)) { + mailbox_send_immediate_signal(target, TrapExceptionSignal, OUT_OF_MEMORY_ATOM); + goto done; + } + + // Build list backwards to preserve input order + term result = term_nil(); + bool build_ok = true; + for (ssize_t i = (ssize_t) signal->len - 1; i >= 0; i--) { + term item_result; + if (UNLIKELY(!context_get_process_info(ctx, &item_result, NULL, signal->atoms[i], &heap))) { + mailbox_send_immediate_signal(target, TrapExceptionSignal, item_result); + build_ok = false; + break; + } + + if (signal->mode == PROCESS_INFO_LIST_OMIT_UNREGISTERED && signal->atoms[i] == REGISTERED_NAME_ATOM && term_is_nil(term_get_tuple_element(item_result, 1))) { + continue; + } + + result = term_list_prepend(item_result, result, &heap); } - } // else: sender died + if (LIKELY(build_ok)) { + mailbox_send_term_signal(target, TrapAnswerSignal, result); + } + memory_destroy_heap(&heap, ctx->global); + } + +done: + if (!process_table_locked) { + globalcontext_get_process_unlock(ctx->global, target); + } } bool context_process_signal_trap_answer(Context *ctx, struct TermSignal *signal) @@ -525,6 +582,7 @@ bool context_get_process_info(Context *ctx, term *out, size_t *term_size, term a case MESSAGE_QUEUE_LEN_ATOM: case REGISTERED_NAME_ATOM: case MEMORY_ATOM: + case TRAP_EXIT_ATOM: ret_size = TUPLE_SIZE(2); break; case LINKS_ATOM: { @@ -589,12 +647,8 @@ bool context_get_process_info(Context *ctx, term *out, size_t *term_size, term a // registered_name for process or port.. case REGISTERED_NAME_ATOM: { term name = globalcontext_get_registered_name_process(ctx->global, ctx->process_id); - if (term_is_invalid_term((name))) { - ret = term_nil(); // Set ret to an empty list to match erlang behaviour - } else { - term_put_tuple_element(ret, 0, REGISTERED_NAME_ATOM); - term_put_tuple_element(ret, 1, name); - } + term_put_tuple_element(ret, 0, REGISTERED_NAME_ATOM); + term_put_tuple_element(ret, 1, term_is_invalid_term(name) ? term_nil() : name); break; } @@ -630,6 +684,13 @@ bool context_get_process_info(Context *ctx, term *out, size_t *term_size, term a break; } + // true if a process traps exits, otherwise false + case TRAP_EXIT_ATOM: { + term_put_tuple_element(ret, 0, TRAP_EXIT_ATOM); + term_put_tuple_element(ret, 1, ctx->trap_exit ? TRUE_ATOM : FALSE_ATOM); + break; + } + // pids of linked processes case LINKS_ATOM: { term_put_tuple_element(ret, 0, LINKS_ATOM); diff --git a/src/libAtomVM/context.h b/src/libAtomVM/context.h index 52ac76d3bd..15c1bf0250 100644 --- a/src/libAtomVM/context.h +++ b/src/libAtomVM/context.h @@ -430,10 +430,10 @@ void context_process_kill_signal(Context *ctx, struct TermSignal *signal); * @brief Process a process info request signal. * * @param ctx the context being executed - * @param signal the process info signal + * @param signal the process info request signal * @param process_table_locked whether process table is already locked */ -void context_process_process_info_request_signal(Context *ctx, struct BuiltInAtomRequestSignal *signal, bool process_table_locked); +void context_process_process_info_request_signal(Context *ctx, struct ProcessInfoRequestSignal *signal, bool process_table_locked); /** * @brief Process a trap answer signal. diff --git a/src/libAtomVM/jit.c b/src/libAtomVM/jit.c index ba1fbfe839..60fd01a541 100644 --- a/src/libAtomVM/jit.c +++ b/src/libAtomVM/jit.c @@ -897,8 +897,8 @@ static Context *jit_process_signal_messages(Context *ctx, JITState *jit_state) break; } case ProcessInfoRequestSignal: { - struct BuiltInAtomRequestSignal *request_signal - = CONTAINER_OF(signal_message, struct BuiltInAtomRequestSignal, base); + struct ProcessInfoRequestSignal *request_signal + = CONTAINER_OF(signal_message, struct ProcessInfoRequestSignal, base); context_process_process_info_request_signal(ctx, request_signal, false); break; } diff --git a/src/libAtomVM/mailbox.c b/src/libAtomVM/mailbox.c index fe256df7ed..2df47f99c8 100644 --- a/src/libAtomVM/mailbox.c +++ b/src/libAtomVM/mailbox.c @@ -110,8 +110,8 @@ void mailbox_message_dispose(MailboxMessage *m, Heap *heap) break; } case ProcessInfoRequestSignal: { - struct BuiltInAtomRequestSignal *request_signal - = CONTAINER_OF(m, struct BuiltInAtomRequestSignal, base); + struct ProcessInfoRequestSignal *request_signal + = CONTAINER_OF(m, struct ProcessInfoRequestSignal, base); free(request_signal); break; } @@ -291,19 +291,24 @@ void mailbox_send_immediate_signal(Context *c, enum MessageType type, term immed mailbox_post_message(c, &immediate_signal->base); } -void mailbox_send_built_in_atom_request_signal( - Context *c, enum MessageType type, int32_t pid, term atom) +void mailbox_send_process_info_request_signal( + Context *c, int32_t sender_pid, process_info_mode_t mode, const term *atoms, size_t len) { - struct BuiltInAtomRequestSignal *atom_request = malloc(sizeof(struct BuiltInAtomRequestSignal)); - if (IS_NULL_PTR(atom_request)) { + struct ProcessInfoRequestSignal *signal = malloc( + sizeof(struct ProcessInfoRequestSignal) + len * sizeof(term)); + if (IS_NULL_PTR(signal)) { fprintf(stderr, "Failed to allocate memory: %s:%i.\n", __FILE__, __LINE__); return; } - atom_request->base.type = type; - atom_request->sender_pid = pid; - atom_request->atom = atom; + signal->base.type = ProcessInfoRequestSignal; + signal->sender_pid = sender_pid; + signal->mode = mode; + signal->len = len; + for (size_t i = 0; i < len; i++) { + signal->atoms[i] = atoms[i]; + } - mailbox_post_message(c, &atom_request->base); + mailbox_post_message(c, &signal->base); } void mailbox_send_ref_signal(Context *c, enum MessageType type, uint64_t ref_ticks) diff --git a/src/libAtomVM/mailbox.h b/src/libAtomVM/mailbox.h index 50e7dd1c2b..62739f2695 100644 --- a/src/libAtomVM/mailbox.h +++ b/src/libAtomVM/mailbox.h @@ -137,12 +137,21 @@ struct ImmediateSignal term immediate; }; -struct BuiltInAtomRequestSignal +typedef enum +{ + PROCESS_INFO_SINGLE, + PROCESS_INFO_LIST, + PROCESS_INFO_LIST_OMIT_UNREGISTERED, +} process_info_mode_t; + +struct ProcessInfoRequestSignal { MailboxMessage base; int32_t sender_pid; - term atom; + process_info_mode_t mode; + size_t len; + term atoms[]; }; struct RefSignal @@ -248,15 +257,16 @@ void mailbox_send_term_signal(Context *c, enum MessageType type, term t); void mailbox_send_immediate_signal(Context *c, enum MessageType type, term immediate); /** - * @brief Sends a built-in atom-based request signal to a certain mailbox. + * @brief Sends a process info request signal to a certain mailbox. * * @param c the process context. - * @param type the type of the signal * @param sender_pid the sender of the signal (to get the answer) - * @param atom the built-in atom + * @param mode controls result shape and registered_name handling + * @param atoms array of atom terms to query + * @param len number of atoms in the array */ -void mailbox_send_built_in_atom_request_signal( - Context *c, enum MessageType type, int32_t sender_pid, term atom); +void mailbox_send_process_info_request_signal( + Context *c, int32_t sender_pid, process_info_mode_t mode, const term *atoms, size_t len); /** * @brief Sends a ref signal to a certain mailbox. diff --git a/src/libAtomVM/nifs.c b/src/libAtomVM/nifs.c index a8fddbcb3c..5d7a4dd046 100644 --- a/src/libAtomVM/nifs.c +++ b/src/libAtomVM/nifs.c @@ -3103,17 +3103,11 @@ static term nif_erlang_processes(Context *ctx, int argc, term argv[]) static term nif_erlang_process_info(Context *ctx, int argc, term argv[]) { - UNUSED(argc); - term pid = argv[0]; - term item_or_item_info = argv[1]; - if (!term_is_atom(item_or_item_info)) { + if (!term_is_pid(pid)) { RAISE_ERROR(BADARG_ATOM); } - // TODO add support for process_info/1 - // and process_info/2 when second argument is a list - term item = item_or_item_info; int local_process_id = term_to_local_process_id(pid); Context *target = globalcontext_get_process_lock(ctx->global, local_process_id); @@ -3121,30 +3115,145 @@ static term nif_erlang_process_info(Context *ctx, int argc, term argv[]) return UNDEFINED_ATOM; } - term ret = term_invalid_term(); - if (ctx == target) { - size_t term_size; - // NOLINT(allocations-without-ensure-free) called with NULL heap, only computes size - if (UNLIKELY(!context_get_process_info(ctx, NULL, &term_size, item, NULL))) { + if (argc == 2 && !term_is_list(argv[1])) { + if (!term_is_atom(argv[1])) { globalcontext_get_process_unlock(ctx->global, target); RAISE_ERROR(BADARG_ATOM); } - if (UNLIKELY(memory_ensure_free_opt(ctx, term_size, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { + term item = argv[1]; + term ret = term_invalid_term(); + if (ctx == target) { + size_t term_size; + if (UNLIKELY(!context_get_process_info(ctx, NULL, &term_size, item, NULL))) { + globalcontext_get_process_unlock(ctx->global, target); + RAISE_ERROR(BADARG_ATOM); + } + if (UNLIKELY(memory_ensure_free_opt(ctx, term_size, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { + globalcontext_get_process_unlock(ctx->global, target); + RAISE_ERROR(OUT_OF_MEMORY_ATOM); + } + if (UNLIKELY(!context_get_process_info(ctx, &ret, NULL, item, &ctx->heap))) { + globalcontext_get_process_unlock(ctx->global, target); + RAISE_ERROR(ret); + } + // return [] when unregistered (BEAM backward compatibility) + if (item == REGISTERED_NAME_ATOM && term_is_nil(term_get_tuple_element(ret, 1))) { + ret = term_nil(); + } + } else { + // Currently, all items require a signal. We could nevertheless filter + // items that do not exist. + mailbox_send_process_info_request_signal(target, ctx->process_id, PROCESS_INFO_SINGLE, &item, 1); + context_update_flags(ctx, ~NoFlags, Trap); + } + globalcontext_get_process_unlock(ctx->global, target); + return ret; + } + + static const term default_items[] = { + REGISTERED_NAME_ATOM, + MESSAGE_QUEUE_LEN_ATOM, + LINKS_ATOM, + TRAP_EXIT_ATOM, + TOTAL_HEAP_SIZE_ATOM, + HEAP_SIZE_ATOM, + STACK_SIZE_ATOM, + }; + + const term *items; + size_t items_len; + term *items_alloc = NULL; + bool omit_unregistered; + process_info_mode_t signal_mode; + + if (argc == 1) { + items = default_items; + items_len = sizeof(default_items) / sizeof(default_items[0]); + omit_unregistered = true; + signal_mode = PROCESS_INFO_LIST_OMIT_UNREGISTERED; + } else { + term item_list = argv[1]; + size_t list_len = 0; + term l = item_list; + + for (; term_is_nonempty_list(l); l = term_get_list_tail(l), list_len++) { + if (UNLIKELY(!term_is_atom(term_get_list_head(l)))) { + globalcontext_get_process_unlock(ctx->global, target); + RAISE_ERROR(BADARG_ATOM); + } + } + + if (UNLIKELY(!term_is_nil(l))) { globalcontext_get_process_unlock(ctx->global, target); - RAISE_ERROR(OUT_OF_MEMORY_ATOM); + RAISE_ERROR(BADARG_ATOM); + } + + if (list_len == 0) { + globalcontext_get_process_unlock(ctx->global, target); + return term_nil(); } - if (UNLIKELY(!context_get_process_info(ctx, &ret, NULL, item, &ctx->heap))) { + + items_alloc = malloc(list_len * sizeof(term)); + if (IS_NULL_PTR(items_alloc)) { globalcontext_get_process_unlock(ctx->global, target); - RAISE_ERROR(ret); + RAISE_ERROR(OUT_OF_MEMORY_ATOM); } - } else { - // Currently, all items require a signal. We could nevertheless filter - // items that do not exist. - mailbox_send_built_in_atom_request_signal(target, ProcessInfoRequestSignal, ctx->process_id, item); + l = item_list; + for (size_t i = 0; i < list_len; i++) { + items_alloc[i] = term_get_list_head(l); + l = term_get_list_tail(l); + } + + items = items_alloc; + items_len = list_len; + omit_unregistered = false; + signal_mode = PROCESS_INFO_LIST; + } + + if (ctx != target) { + mailbox_send_process_info_request_signal(target, ctx->process_id, signal_mode, items, items_len); context_update_flags(ctx, ~NoFlags, Trap); + free(items_alloc); + globalcontext_get_process_unlock(ctx->global, target); + return term_invalid_term(); } - globalcontext_get_process_unlock(ctx->global, target); + size_t total_size = 0; + for (size_t i = 0; i < items_len; i++) { + size_t item_size; + if (UNLIKELY(!context_get_process_info(ctx, NULL, &item_size, items[i], NULL))) { + free(items_alloc); + globalcontext_get_process_unlock(ctx->global, target); + RAISE_ERROR(BADARG_ATOM); + } + total_size += item_size + CONS_SIZE; + } + + if (UNLIKELY(memory_ensure_free_opt(ctx, total_size, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { + free(items_alloc); + globalcontext_get_process_unlock(ctx->global, target); + RAISE_ERROR(OUT_OF_MEMORY_ATOM); + } + + // Atoms in `items` are immediate, safe after GC + term ret = term_nil(); + for (ssize_t i = (ssize_t) items_len - 1; i >= 0; i--) { + term item_result; + if (UNLIKELY(!context_get_process_info(ctx, &item_result, NULL, items[i], &ctx->heap))) { + free(items_alloc); + globalcontext_get_process_unlock(ctx->global, target); + RAISE_ERROR(item_result); + } + + if (omit_unregistered && items[i] == REGISTERED_NAME_ATOM && term_is_nil(term_get_tuple_element(item_result, 1))) { + continue; + } + + ret = term_list_prepend(item_result, ret, &ctx->heap); + } + + free(items_alloc); + globalcontext_get_process_unlock(ctx->global, target); return ret; } diff --git a/src/libAtomVM/nifs.gperf b/src/libAtomVM/nifs.gperf index 40fdb911c2..22c6e37e08 100644 --- a/src/libAtomVM/nifs.gperf +++ b/src/libAtomVM/nifs.gperf @@ -125,6 +125,7 @@ erlang:timestamp/0, ×tamp_nif erlang:process_flag/2, &process_flag_nif erlang:process_flag/3, &process_flag_nif erlang:processes/0, &processes_nif +erlang:process_info/1, &process_info_nif erlang:process_info/2, &process_info_nif erlang:get/0, &get_0_nif erlang:put/2, &put_nif diff --git a/src/libAtomVM/opcodesswitch.h b/src/libAtomVM/opcodesswitch.h index 54a5395aaf..259211cca8 100644 --- a/src/libAtomVM/opcodesswitch.h +++ b/src/libAtomVM/opcodesswitch.h @@ -1158,8 +1158,8 @@ static void destroy_extended_registers(Context *ctx, unsigned int live) break; \ } \ case ProcessInfoRequestSignal: { \ - struct BuiltInAtomRequestSignal *request_signal \ - = CONTAINER_OF(signal_message, struct BuiltInAtomRequestSignal, base); \ + struct ProcessInfoRequestSignal *request_signal \ + = CONTAINER_OF(signal_message, struct ProcessInfoRequestSignal, base); \ context_process_process_info_request_signal(ctx, request_signal, false); \ break; \ } \ diff --git a/src/libAtomVM/scheduler.c b/src/libAtomVM/scheduler.c index a856d60012..69b69928a9 100644 --- a/src/libAtomVM/scheduler.c +++ b/src/libAtomVM/scheduler.c @@ -90,8 +90,8 @@ static void scheduler_process_native_signal_messages(Context *ctx) break; } case ProcessInfoRequestSignal: { - struct BuiltInAtomRequestSignal *request_signal - = CONTAINER_OF(signal_message, struct BuiltInAtomRequestSignal, base); + struct ProcessInfoRequestSignal *request_signal + = CONTAINER_OF(signal_message, struct ProcessInfoRequestSignal, base); context_process_process_info_request_signal(ctx, request_signal, false); break; } diff --git a/tests/erlang_tests/test_process_info.erl b/tests/erlang_tests/test_process_info.erl index be098e2f25..d2a8f5a13e 100644 --- a/tests/erlang_tests/test_process_info.erl +++ b/tests/erlang_tests/test_process_info.erl @@ -23,74 +23,216 @@ -export([start/0, loop/2]). start() -> - Self = self(), - {Pid, Ref} = spawn_opt(?MODULE, loop, [Self, []], [monitor]), - receive - ok -> ok - end, - [] = erlang:process_info(Self, registered_name), - erlang:register(has_name, Self), - {registered_name, Name} = erlang:process_info(Self, registered_name), - assert(Name =:= has_name), - erlang:unregister(has_name), - [] = erlang:process_info(Self, registered_name), - test_message_queue_len(Pid, Self), - {links, []} = process_info(Pid, links), - link(Pid), - {links, [Self]} = process_info(Pid, links), - unlink(Pid), - {links, []} = process_info(Pid, links), - Monitor = monitor(process, Pid), - % Twice because of spawn_opt above - {monitored_by, [Self, Self]} = process_info(Pid, monitored_by), - demonitor(Monitor), - {monitored_by, [Self]} = process_info(Pid, monitored_by), - Pid ! {Self, stop}, - _Accum = - receive - {Pid, result, X} -> X + ok = test_process_info_1(), + + ok = test_registered_name(), + ok = test_heap_size(), + ok = test_memory(), + ok = test_total_heap_size(), + ok = test_message_queue_len(), + ok = test_links(), + ok = test_monitored_by(), + + ok = test_list_semantics(), + ok = test_badargs(), + + 0. + +with_other_pid(Fun) -> + {Pid, Ref} = spawn_opt( + fun() -> + receive + quit -> ok + end end, + [monitor] + ), + Fun(Pid), + Pid ! quit, normal = receive {'DOWN', Ref, process, Pid, Reason} -> Reason + end. + +with_dead_pid(Fun) -> + {DeadPid, Ref} = spawn_opt(fun() -> ok end, [monitor]), + normal = + receive + {'DOWN', Ref, process, DeadPid, Reason} -> Reason end, - MessageQueueLen = process_info(Pid, message_queue_len), - MessageQueueLen = undefined, - ok = test_process_info_memory_other(), - 0. + Fun(DeadPid). + +get_item(Pid, Item) -> + {Item, Val} = process_info(Pid, Item), + [{Item, Val}] = process_info(Pid, [Item]), + Val. + +test_process_info_1() -> + Check = fun(Pid) -> + Info = process_info(Pid), + true = is_list(Info), + + {heap_size, HS} = lists:keyfind(heap_size, 1, Info), + true = is_integer(HS) andalso HS > 0, + + {total_heap_size, THS} = lists:keyfind(total_heap_size, 1, Info), + true = THS >= HS, + + {stack_size, SS} = lists:keyfind(stack_size, 1, Info), + true = is_integer(SS) andalso SS >= 0, + + {message_queue_len, MQL} = lists:keyfind(message_queue_len, 1, Info), + true = is_integer(MQL) andalso MQL >= 0, + + {links, Links} = lists:keyfind(links, 1, Info), + true = is_list(Links), + + {trap_exit, TE} = lists:keyfind(trap_exit, 1, Info), + true = is_boolean(TE), + + false = lists:keyfind(registered_name, 1, Info) + end, + + Check(self()), + % with_other_pid(Check), + + with_dead_pid(fun(DeadPid) -> + undefined = process_info(DeadPid) + end), + + ok. + +test_registered_name() -> + Check = fun(Pid) -> + [] = process_info(Pid, registered_name), + [{registered_name, []}] = process_info(Pid, [registered_name]), + + erlang:register(has_name, Pid), + has_name = get_item(Pid, registered_name), + + erlang:unregister(has_name), + [] = process_info(Pid, registered_name), + [{registered_name, []}] = process_info(Pid, [registered_name]) + end, + + Check(self()), + with_other_pid(Check), + + ok. + +test_heap_size() -> + Self = self(), + HS = get_item(Self, heap_size), + true = is_integer(HS) andalso HS > 0, + + with_other_pid(fun(Pid) -> + HS2 = get_item(Pid, heap_size), + true = is_integer(HS2) andalso HS2 > 0 + end), + + with_dead_pid(fun(DeadPid) -> + undefined = process_info(DeadPid, heap_size) + end), + + ok. + +test_memory() -> + Self = self(), + M = get_item(Self, memory), + true = is_integer(M) andalso M > 0, + + with_other_pid(fun(Pid) -> + M2 = get_item(Pid, memory), + true = is_integer(M2) andalso M2 > 0 + end), + + with_dead_pid(fun(DeadPid) -> + undefined = process_info(DeadPid, memory) + end), + + ok. + +test_total_heap_size() -> + Self = self(), + HS = get_item(Self, heap_size), + THS = get_item(Self, total_heap_size), + true = HS =< THS, + + with_other_pid(fun(Pid) -> + HS2 = get_item(Pid, heap_size), + THS2 = get_item(Pid, total_heap_size), + true = HS2 =< THS2, + verify_stable_total_heap_size(Pid, THS2, 50) + end), + + with_dead_pid(fun(DeadPid) -> + undefined = process_info(DeadPid, total_heap_size) + end), + + ok. + +verify_stable_total_heap_size(_Pid, _THS, 0) -> + ok; +verify_stable_total_heap_size(Pid, THS, N) -> + {total_heap_size, THS} = process_info(Pid, total_heap_size), + verify_stable_total_heap_size(Pid, THS, N - 1). + +test_message_queue_len() -> + Self = self(), + {Pid, Ref} = spawn_opt(?MODULE, loop, [Self, []], [monitor]), + receive + ok -> ok + end, + + Q0 = get_item(Pid, message_queue_len), -test_message_queue_len(Pid, Self) -> - {message_queue_len, MessageQueueLen} = process_info(Pid, message_queue_len), - {memory, Memory} = process_info(Pid, memory), - {heap_size, HeapSize} = process_info(Pid, heap_size), - {total_heap_size, TotalHeapSize} = process_info(Pid, total_heap_size), Pid ! incr, Pid ! incr, Pid ! incr, - {message_queue_len, MessageQueueLen2} = process_info(Pid, message_queue_len), - {memory, Memory2} = process_info(Pid, memory), + + Q1 = get_item(Pid, message_queue_len), + true = Q0 < Q1, + Pid ! unlock, Pid ! {Self, ping}, receive pong -> ok end, - {total_heap_size, TotalHeapSize2} = process_info(Pid, total_heap_size), - {heap_size, HeapSize2} = process_info(Pid, heap_size), - true = MessageQueueLen < MessageQueueLen2, - case erlang:system_info(machine) of - "BEAM" -> - true = Memory =< Memory2, - true = HeapSize =< TotalHeapSize, - true = HeapSize2 =< TotalHeapSize2, - true = TotalHeapSize =< TotalHeapSize2; - _ -> - true = Memory < Memory2, - true = HeapSize =< TotalHeapSize, - true = HeapSize2 =< TotalHeapSize2, - true = TotalHeapSize =< TotalHeapSize2 - end. + Q2 = get_item(Pid, message_queue_len), + true = Q2 < Q1, + + Pid ! {Self, stop}, + receive + {Pid, result, _} -> ok + end, + normal = + receive + {'DOWN', Ref, process, Pid, Reason} -> Reason + end, + + undefined = process_info(Pid, message_queue_len), + + ok. + +test_links() -> + Self = self(), + + with_other_pid(fun(Pid) -> + [] = get_item(Pid, links), + + link(Pid), + [Self] = get_item(Pid, links), + + unlink(Pid), + [] = get_item(Pid, links) + end), + + ok. + +test_monitored_by() -> + Self = self(), + [] = get_item(Self, monitored_by), -test_process_info_memory_other() -> {Pid, Ref} = spawn_opt( fun() -> receive @@ -99,20 +241,65 @@ test_process_info_memory_other() -> end, [monitor] ), - {total_heap_size, THS0} = process_info(Pid, total_heap_size), - test_process_info_memory_other_loop(Pid, THS0, 50), + + % spawn_opt with [monitor] counts as one monitor from Self + [Self] = get_item(Pid, monitored_by), + + Mon = monitor(process, Pid), + [Self, Self] = get_item(Pid, monitored_by), + + demonitor(Mon), + [Self] = get_item(Pid, monitored_by), + Pid ! quit, normal = receive {'DOWN', Ref, process, Pid, Reason} -> Reason end, + ok. -test_process_info_memory_other_loop(_Pid, _THS0, 0) -> - ok; -test_process_info_memory_other_loop(Pid, THS0, N) -> - {total_heap_size, THS0} = process_info(Pid, total_heap_size), - test_process_info_memory_other_loop(Pid, THS0, N - 1). +test_list_semantics() -> + Self = self(), + + [] = process_info(Self, []), + + [{heap_size, _}, {memory, _}] = process_info(Self, [heap_size, memory]), + [{memory, _}, {heap_size, _}] = process_info(Self, [memory, heap_size]), + + [{total_heap_size, THS}, {total_heap_size, THS}] = + process_info(Self, [total_heap_size, total_heap_size]), + + with_other_pid(fun(Pid) -> + [] = process_info(Pid, []), + [{message_queue_len, _}, {heap_size, _}, {memory, _}] = + process_info(Pid, [message_queue_len, heap_size, memory]) + end), + + with_dead_pid(fun(DeadPid) -> + undefined = process_info(DeadPid, []), + undefined = process_info(DeadPid, [heap_size]), + undefined = process_info(DeadPid, [heap_size, memory]) + end), + + ok. + +test_badargs() -> + Self = self(), + + assert_badarg(fun() -> process_info(bad_pid) end), + assert_badarg(fun() -> process_info(bad_pid, heap_size) end), + assert_badarg(fun() -> process_info(Self, bad_item) end), + + assert_badarg(fun() -> process_info(Self, 42) end), + assert_badarg(fun() -> process_info(Self, {heap_size}) end), + + assert_badarg(fun() -> process_info(bad_pid, [heap_size]) end), + assert_badarg(fun() -> process_info(Self, [heap_size, 42]) end), + assert_badarg(fun() -> process_info(Self, [heap_size, invalid_key]) end), + assert_badarg(fun() -> process_info(Self, [heap_size | not_a_list]) end), + + ok. loop(undefined, Accum) -> receive @@ -132,4 +319,13 @@ loop(Pid, Accum) -> Pid ! ok, loop(locked, Accum). -assert(true) -> ok. +assert_badarg(Fun) -> + try + Fun(), + erlang:error(no_throw) + catch + error:badarg -> + ok; + OtherClass:OtherError -> + erlang:error({OtherClass, OtherError}) + end.