Skip to content

Commit 34732fa

Browse files
committed
erts: Fair handover of code permissions to first waiter
No more simple release-the-herd that could cause very bad latency's (seen thousands of failed retries in tprof_SUITE). At retry, the BIF must UNCONDITIONALLY call erts_try_seize_code_*_permission. Checked all call sites and fixed the ones in erl_bif_trace.c accordingly, to seize permission *before* checking if trace session is still alive. A handover-ed permission will be automatically released if the process exits *before* the BIF has been called. But after that, there is still no automatic release of seized permissions for exiting processes. Why? Because the job protected by the permission must be completed even if the process dies. Which means the BIF C function must either release the permission before returning or schedule aux jobs that are sure to complete the job and release the permission.
1 parent 550d7b7 commit 34732fa

13 files changed

Lines changed: 759 additions & 146 deletions

File tree

erts/emulator/beam/code_ix.c

Lines changed: 189 additions & 79 deletions
Large diffs are not rendered by default.

erts/emulator/beam/code_ix.h

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ ErtsCodeIndex erts_staging_code_ix(void);
175175
* System thread progress must not be blocked.
176176
* Caller must not already have the code modification or staging permissions.
177177
* Caller is suspended and *must* yield if 0 is returned. */
178-
int erts_try_seize_code_load_permission(struct process* c_p);
178+
bool erts_try_seize_code_load_permission(struct process* c_p);
179179

180180
/** @brief Release code loading permission. Resumes any suspended waiters. */
181181
void erts_release_code_load_permission(void);
@@ -188,15 +188,20 @@ void erts_release_code_load_permission(void);
188188
*
189189
* * Main process lock (only) must be held.
190190
* * System thread progress must not be blocked.
191-
* * Caller is suspended and *must* yield if 0 is returned.
192191
* * Caller must not already have the code modification or staging permissions.
193192
*
194193
* That is, it is _NOT_ possible to add code modification permission when you
195194
* already have staging permission. The other way around is fine however.
195+
*
196+
* If true is returned, calling BIF must either release before returning or
197+
* schedule aux job that will eventually release.
198+
*
199+
* If false is returned, caller is suspended and *must* schedule itself to
200+
* UNCONDITIONALLY call this function again.
196201
*/
197-
int erts_try_seize_code_stage_permission(struct process* c_p);
202+
bool erts_try_seize_code_stage_permission(struct process* c_p);
198203

199-
/** @brief Release code stage permission. Resumes any suspended waiters. */
204+
/** @brief Release code stage permission. Resumes first suspended waiter. */
200205
void erts_release_code_stage_permission(void);
201206

202207
/** @brief Try to seize exclusive code modification permission. Needed for
@@ -207,11 +212,17 @@ void erts_release_code_stage_permission(void);
207212
*
208213
* * Main process lock (only) must be held.
209214
* * System thread progress must not be blocked.
210-
* * Caller is suspended and *must* yield if 0 is returned.
211215
* * Caller must not already have the code modification permission, but may
212216
* have staging permission.
217+
*
218+
* If true is returned, calling BIF must either release before returning
219+
* or schedule aux job that will eventually release.
220+
*
221+
* If false is returned, caller is suspended and *must* schedule itself to
222+
* UNCONDITIONALLY call this function again.
223+
*
213224
*/
214-
int erts_try_seize_code_mod_permission(struct process* c_p);
225+
bool erts_try_seize_code_mod_permission(struct process* c_p);
215226

216227
/** @brief As \c erts_try_seize_code_mod_permission but for aux work.
217228
*
@@ -220,7 +231,7 @@ int erts_try_seize_code_mod_permission(struct process* c_p);
220231
* On failure return false and aux work func(arg) will be scheduled when
221232
* permission is released.
222233
*/
223-
int erts_try_seize_code_mod_permission_aux(void (*func)(void *),
234+
bool erts_try_seize_code_mod_permission_aux(void (*func)(void *),
224235
void *arg);
225236

226237
#ifdef ERTS_ENABLE_LOCK_CHECK
@@ -229,10 +240,21 @@ void erts_lc_soften_code_mod_permission_check(void);
229240
# define erts_lc_soften_code_mod_permission_check() ((void)0)
230241
#endif
231242

232-
/** @brief Release code modification permission. Resumes any suspended
233-
* waiters. */
243+
/** @brief Release code modification permission. Resumes first suspended
244+
* waiter.
245+
*/
234246
void erts_release_code_mod_permission(void);
235247

248+
/** @brief Reject all code permissions. Resumes first suspended waiters.
249+
* Is called by exiting or garbing process that got permission(s)
250+
* but don't want it. A garbing process should be scheduled to try again
251+
* to seize permission after GC is done.
252+
*
253+
* @param c_p[in] Current process.
254+
*/
255+
void erts_reject_code_permissions(struct process* c_p);
256+
257+
236258
/* Prepare the "staging area" to be a complete copy of the active code.
237259
*
238260
* Code staging permission must have been seized.

erts/emulator/beam/erl_bif_info.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4583,6 +4583,10 @@ BIF_RETTYPE erts_debug_get_internal_state_1(BIF_ALIST_1)
45834583
else if (ERTS_IS_ATOM_STR("debugger_support", BIF_ARG_1)) {
45844584
return erts_debugger_flags & ERTS_DEBUGGER_ENABLED ? am_true : am_false;
45854585
}
4586+
else if (ERTS_IS_ATOM_STR("dirty_gc_limit", BIF_ARG_1)) {
4587+
ASSERT(IS_SSMALL(ERTS_POTENTIALLY_LONG_GC_HSIZE));
4588+
return make_small(ERTS_POTENTIALLY_LONG_GC_HSIZE);
4589+
}
45864590
}
45874591
else if (is_tuple(BIF_ARG_1)) {
45884592
Eterm* tp = tuple_val(BIF_ARG_1);

erts/emulator/beam/erl_bif_trace.c

Lines changed: 23 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -316,28 +316,18 @@ erts_internal_trace_pattern_4(BIF_ALIST_4)
316316
{
317317
ErtsTraceSession* session;
318318

319-
if (!term_to_session(BIF_ARG_1, &session, false)) {
320-
goto session_error;
321-
}
322-
323319
if (!erts_try_seize_code_mod_permission(BIF_P)) {
324-
erts_deref_trace_session(session);
325320
ERTS_BIF_YIELD4(BIF_TRAP_EXPORT(BIF_erts_internal_trace_pattern_4),
326321
BIF_P, BIF_ARG_1, BIF_ARG_2, BIF_ARG_3, BIF_ARG_4);
327322
}
328323

329-
/* Double check liveness with seized code mod permission */
330-
if (!erts_is_trace_session_alive(session)) {
324+
if (!term_to_session(BIF_ARG_1, &session, false)) {
331325
erts_release_code_mod_permission();
332-
erts_deref_trace_session(session);
333-
goto session_error;
326+
BIF_P->fvalue = am_session;
327+
BIF_ERROR(BIF_P, BADARG | EXF_HAS_EXT_INFO);
334328
}
335329

336330
return trace_pattern(BIF_P, session, BIF_ARG_2, BIF_ARG_3, BIF_ARG_4);
337-
338-
session_error:
339-
BIF_P->fvalue = am_session;
340-
BIF_ERROR(BIF_P, BADARG | EXF_HAS_EXT_INFO);
341331
}
342332

343333
static Eterm
@@ -911,30 +901,21 @@ Eterm erts_internal_trace_4(BIF_ALIST_4)
911901
ErtsTraceSession* session;
912902
Eterm ret;
913903

914-
if (!term_to_session(BIF_ARG_1, &session, false)) {
915-
goto session_error;
916-
}
917904
if (!erts_try_seize_code_mod_permission(BIF_P)) {
918-
erts_deref_trace_session(session);
919905
ERTS_BIF_YIELD4(BIF_TRAP_EXPORT(BIF_erts_internal_trace_4),
920906
BIF_P, BIF_ARG_1, BIF_ARG_2, BIF_ARG_3, BIF_ARG_4);
921907
}
922908

923-
/* Double check liveness with seized code mod permission */
924-
if (!erts_is_trace_session_alive(session)) {
909+
if (!term_to_session(BIF_ARG_1, &session, false)) {
925910
erts_release_code_mod_permission();
926-
erts_deref_trace_session(session);
927-
goto session_error;
911+
BIF_P->fvalue = am_session;
912+
BIF_ERROR(BIF_P, BADARG | EXF_HAS_EXT_INFO);
928913
}
929914

930915
ret = trace(BIF_P, session, BIF_ARG_2, BIF_ARG_3, BIF_ARG_4);
931916

932917
erts_deref_trace_session(session);
933918
return ret;
934-
935-
session_error:
936-
BIF_P->fvalue = am_session;
937-
BIF_ERROR(BIF_P, BADARG | EXF_HAS_EXT_INFO);
938919
}
939920

940921
static
@@ -1298,19 +1279,24 @@ Eterm
12981279
erts_internal_trace_session_destroy_1(BIF_ALIST_1)
12991280
{
13001281
ErtsTraceSession* session;
1282+
1283+
if (!erts_try_seize_code_mod_permission(BIF_P)) {
1284+
ERTS_BIF_YIELD1(BIF_TRAP_EXPORT(BIF_erts_internal_trace_session_destroy_1),
1285+
BIF_P, BIF_ARG_1);
1286+
}
1287+
13011288
if (!term_to_session(BIF_ARG_1, &session, true)) {
1289+
erts_release_code_mod_permission();
13021290
BIF_P->fvalue = am_badopt;
13031291
BIF_ERROR(BIF_P, BADARG | EXF_HAS_EXT_INFO);
13041292
}
13051293
if (!erts_is_trace_session_alive(session)) {
1294+
erts_release_code_mod_permission();
13061295
erts_deref_trace_session(session);
13071296
BIF_RET(am_false);
13081297
}
1309-
if (!erts_try_seize_code_mod_permission(BIF_P)) {
1310-
erts_deref_trace_session(session);
1311-
ERTS_BIF_YIELD1(BIF_TRAP_EXPORT(BIF_erts_internal_trace_session_destroy_1),
1312-
BIF_P, BIF_ARG_1);
1313-
}
1298+
1299+
13141300
if (erts_atomic_cmpxchg_nob(&session->state,
13151301
ERTS_TRACE_SESSION_CLEARING,
13161302
ERTS_TRACE_SESSION_ALIVE)
@@ -1423,27 +1409,19 @@ Eterm erts_internal_trace_info_3(BIF_ALIST_3)
14231409
bool to_be_continued = false;
14241410
Eterm ret;
14251411

1426-
if (BIF_ARG_1 == am_any) {
1427-
/* trace:session_info */
1428-
session = NULL;
1429-
}
1430-
else if (!term_to_session(BIF_ARG_1, &session, true)) {
1431-
goto session_error;
1432-
}
1433-
14341412
if (!erts_try_seize_code_mod_permission(BIF_P)) {
1435-
if (session) {
1436-
erts_deref_trace_session(session);
1437-
}
14381413
ERTS_BIF_YIELD3(BIF_TRAP_EXPORT(BIF_erts_internal_trace_info_3),
14391414
BIF_P, BIF_ARG_1, BIF_ARG_2, BIF_ARG_3);
14401415
}
14411416

1442-
/* Double check session liveness with seized code mod permission */
1443-
if (session && !erts_is_trace_session_alive(session)) {
1417+
if (BIF_ARG_1 == am_any) {
1418+
/* trace:session_info */
1419+
session = NULL;
1420+
}
1421+
else if (!term_to_session(BIF_ARG_1, &session, false)) {
14441422
erts_release_code_mod_permission();
1445-
erts_deref_trace_session(session);
1446-
goto session_error;
1423+
BIF_P->fvalue = am_session;
1424+
BIF_ERROR(BIF_P, BADARG | EXF_HAS_EXT_INFO);
14471425
}
14481426

14491427
ret = trace_info(BIF_P, session, BIF_ARG_2, BIF_ARG_3, &to_be_continued);
@@ -1454,10 +1432,6 @@ Eterm erts_internal_trace_info_3(BIF_ALIST_3)
14541432
}
14551433
}
14561434
return ret;
1457-
1458-
session_error:
1459-
BIF_P->fvalue = am_session;
1460-
BIF_ERROR(BIF_P, BADARG | EXF_HAS_EXT_INFO);
14611435
}
14621436

14631437
static

erts/emulator/beam/erl_gc.c

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,37 @@ check_for_possibly_long_gc(Process *p, Uint ygen_usage)
715715
}
716716
}
717717

718+
static void set_proc_state_gc(Process* p, bool is_garbing)
719+
{
720+
/*
721+
* Q: Why two GC flags? One in 'state' and one in 'xstate'?
722+
*
723+
* A: Just code convenience. We needed a GC flag in 'xstate' to be accessed
724+
* atomically together with the HANDOVER_CODE_*_PERM flags. The old one
725+
* in 'state' could be replaced by the new one in 'xstate' but that
726+
* would require refactoring all the places where it's currently read.
727+
*/
728+
if (is_garbing) {
729+
const erts_aint32_t old_xstate =
730+
erts_atomic32_read_bor_nob(&p->xstate, ERTS_PXSFLG_GC);
731+
erts_atomic32_read_bor_nob(&p->state, ERTS_PSFLG_GC);
732+
733+
if (old_xstate & (ERTS_PXSFLG_HANDOVER_CODE_MOD_PERM |
734+
ERTS_PXSFLG_HANDOVER_CODE_STAGE_PERM)) {
735+
/*
736+
* We don't want to hold code permissions during a potentially
737+
* long GC. Process will retry to seize permission(s) after GC is
738+
* done. This can only happen with a race between other processes
739+
* giving us code permission and sending us GC signal.
740+
*/
741+
erts_reject_code_permissions(p);
742+
}
743+
}
744+
else {
745+
erts_atomic32_read_band_nob(&p->xstate, ~ERTS_PXSFLG_GC);
746+
erts_atomic32_read_band_nob(&p->state, ~ERTS_PSFLG_GC);
747+
}
748+
}
718749

719750
/*
720751
* Garbage collect a process.
@@ -768,7 +799,7 @@ garbage_collect(Process* p, ErlHeapFragment *live_hf_end,
768799

769800
ERTS_MSACC_SET_STATE_CACHED(ERTS_MSACC_STATE_GC);
770801

771-
erts_atomic32_read_bor_nob(&p->state, ERTS_PSFLG_GC);
802+
set_proc_state_gc(p, true);
772803
if (erts_system_monitor_long_gc)
773804
start_time = erts_get_monotonic_time(esdp);
774805

@@ -849,7 +880,7 @@ garbage_collect(Process* p, ErlHeapFragment *live_hf_end,
849880
delay_gc_after_start:
850881
/* erts_send_exit_signal looks for ERTS_PSFLG_GC, so
851882
we have to remove it after the signal is sent */
852-
erts_atomic32_read_band_nob(&p->state, ~ERTS_PSFLG_GC);
883+
set_proc_state_gc(p, false);
853884

854885
/* We have to make sure that we have space for need on the heap */
855886
res = delay_garbage_collection(p, need, fcalls);
@@ -864,7 +895,7 @@ garbage_collect(Process* p, ErlHeapFragment *live_hf_end,
864895
ERTS_CHK_OFFHEAP(p);
865896
ErtsGcQuickSanityCheck(p);
866897

867-
erts_atomic32_read_band_nob(&p->state, ~ERTS_PSFLG_GC);
898+
set_proc_state_gc(p, false);
868899

869900
if (ERTS_IS_P_TRACED_FL(p, F_TRACE_GC)) {
870901
trace_gc(p, gc_trace_end_tag, reclaimed_now, THE_NON_VALUE);
@@ -1006,7 +1037,7 @@ garbage_collect_hibernate(Process* p, int check_long_gc)
10061037
p->flags = flags;
10071038
}
10081039

1009-
erts_atomic32_read_bor_nob(&p->state, ERTS_PSFLG_GC);
1040+
set_proc_state_gc(p, true);
10101041
ErtsGcQuickSanityCheck(p);
10111042

10121043
heap_size = p->heap_sz + (p->old_htop - p->old_heap) + p->mbuf_sz;
@@ -1089,7 +1120,7 @@ garbage_collect_hibernate(Process* p, int check_long_gc)
10891120

10901121
ErtsGcQuickSanityCheck(p);
10911122

1092-
erts_atomic32_read_band_nob(&p->state, ~ERTS_PSFLG_GC);
1123+
set_proc_state_gc(p, false);
10931124

10941125
return gc_cost(final_size, final_size);
10951126
}
@@ -1182,7 +1213,7 @@ erts_garbage_collect_literals(Process* p, Eterm* literals,
11821213
/*
11831214
* Set GC state.
11841215
*/
1185-
erts_atomic32_read_bor_nob(&p->state, ERTS_PSFLG_GC);
1216+
set_proc_state_gc(p, true);
11861217

11871218
/*
11881219
* Just did a major collection (which has discarded the old heap),
@@ -1349,7 +1380,7 @@ erts_garbage_collect_literals(Process* p, Eterm* literals,
13491380
/*
13501381
* Restore status.
13511382
*/
1352-
erts_atomic32_read_band_nob(&p->state, ~ERTS_PSFLG_GC);
1383+
set_proc_state_gc(p, false);
13531384

13541385
reds += (Sint64) gc_cost((p->htop - p->heap) + byte_lit_size/sizeof(Uint), 0);
13551386

erts/emulator/beam/erl_gc.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@
2323
#ifndef __ERL_GC_H__
2424
#define __ERL_GC_H__
2525

26+
#define ERTS_POTENTIALLY_LONG_GC_HSIZE (128*1024) /* Words */
27+
2628
#if defined(ERL_WANT_GC_INTERNALS__) || defined(ERTS_DO_INCL_GLB_INLINE_FUNC_DEF)
2729

2830
/* GC declarations used by beam/erl_gc.c */
2931

30-
#define ERTS_POTENTIALLY_LONG_GC_HSIZE (128*1024) /* Words */
31-
3232
#include "erl_map.h"
3333
#include "erl_fun.h"
3434
#include "erl_bits.h"

erts/emulator/beam/erl_process.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14212,6 +14212,11 @@ erts_do_exit_process(Process* p, Eterm reason)
1421214212

1421314213
erts_proc_unlock(p, ERTS_PROC_LOCKS_ALL_MINOR);
1421414214

14215+
if (erts_atomic32_read_nob(&p->xstate) & (ERTS_PXSFLG_HANDOVER_CODE_MOD_PERM |
14216+
ERTS_PXSFLG_HANDOVER_CODE_STAGE_PERM)) {
14217+
erts_reject_code_permissions(p);
14218+
}
14219+
1421514220
if (ERTS_IS_P_TRACED_FL(p, F_TRACE_PROCS))
1421614221
trace_proc(p, ERTS_PROC_LOCK_MAIN, p, am_exit, reason);
1421714222

erts/emulator/beam/erl_process.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1404,6 +1404,15 @@ void erts_check_for_holes(Process* p);
14041404
/* MAYBE_SELF_SIGS - We might have outstanding signals
14051405
from ourselves to ourselves. */
14061406
#define ERTS_PXSFLG_MAYBE_SELF_SIGS (((erts_aint32_t) 1) << 8)
1407+
/* HANDOVER_CODE_MOD_PERM - Waiting process was given the code mod permission
1408+
but has not yet seized the lock. */
1409+
#define ERTS_PXSFLG_HANDOVER_CODE_MOD_PERM (((erts_aint32_t) 1) << 9)
1410+
/* HANDOVER_CODE_STAGE_PERM - Waiting process was given the code stage permission
1411+
but has not yet seized the lock. */
1412+
#define ERTS_PXSFLG_HANDOVER_CODE_STAGE_PERM (((erts_aint32_t) 1) << 10)
1413+
/* GC - Process is garbage collecting */
1414+
#define ERTS_PXSFLG_GC (((erts_aint32_t) 1) << 11)
1415+
14071416

14081417
#define ERTS_PXSFLGS_QMASK ERTS_PSFLGS_QMASK
14091418
#define ERTS_PXSFLGS_IN_CPU_PRQ_MASK_OFFSET 0

erts/emulator/test/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ MODULES= \
146146
erts_test_destructor \
147147
crypto_reference \
148148
literal_area_collector_test \
149+
erts_test_sync_tracer \
149150
ext_records
150151

151152
NO_OPT= bs_bincomp \

0 commit comments

Comments
 (0)