From 38ddf72b48377bd6b2fb8c4b6981360ae7d44d79 Mon Sep 17 00:00:00 2001
From: Sverker Eriksson <sverker@erlang.org>
Date: Mon, 8 Dec 2014 15:38:04 +0100
Subject: erts: Add alloc_SUITE:migration

---
 erts/emulator/beam/erl_alloc.c                     |  27 +++
 erts/emulator/beam/erl_alloc_util.c                |  10 +
 erts/emulator/test/alloc_SUITE.erl                 |  78 +++++--
 erts/emulator/test/alloc_SUITE_data/Makefile.src   |   3 +-
 .../test/alloc_SUITE_data/allocator_test.h         |   2 +
 erts/emulator/test/alloc_SUITE_data/migration.c    | 229 +++++++++++++++++++++
 .../test/alloc_SUITE_data/testcase_driver.c        |  14 ++
 .../test/alloc_SUITE_data/testcase_driver.h        |   1 +
 8 files changed, 350 insertions(+), 14 deletions(-)
 create mode 100644 erts/emulator/test/alloc_SUITE_data/migration.c

diff --git a/erts/emulator/beam/erl_alloc.c b/erts/emulator/beam/erl_alloc.c
index 4223b357f1..0aa45acd82 100644
--- a/erts/emulator/beam/erl_alloc.c
+++ b/erts/emulator/beam/erl_alloc.c
@@ -3615,6 +3615,33 @@ UWord erts_alc_test(UWord op, UWord a1, UWord a2, UWord a3)
 
 	case 0xf15: erts_free(ERTS_ALC_T_TEST, (void*)a1); return 0;
 
+	case 0xf16: {
+            Uint extra_hdr_sz = UNIT_CEILING((Uint)a1);
+	    ErtsAllocatorThrSpec_t* ts = &erts_allctr_thr_spec[ERTS_ALC_A_TEST];
+	    Uint offset = ts->allctr[0]->mbc_header_size;
+	    void* orig_creating_mbc = ts->allctr[0]->creating_mbc;
+	    void* orig_destroying_mbc = ts->allctr[0]->destroying_mbc;
+	    void* new_creating_mbc = *(void**)a2; /* inout arg */
+	    void* new_destroying_mbc = *(void**)a3; /* inout arg */
+	    int i;
+
+	    for (i=0; i < ts->size; i++) {
+		Allctr_t* ap = ts->allctr[i];
+		if (ap->mbc_header_size != offset
+		    || ap->creating_mbc != orig_creating_mbc
+		    || ap->destroying_mbc != orig_destroying_mbc
+		    || ap->mbc_list.first != NULL)
+		    return -1;
+	    }
+	    for (i=0; i < ts->size; i++) {
+		ts->allctr[i]->mbc_header_size += extra_hdr_sz;
+		ts->allctr[i]->creating_mbc = new_creating_mbc;
+		ts->allctr[i]->destroying_mbc = new_destroying_mbc;
+	    }
+	    *(void**)a2 = orig_creating_mbc;
+	    *(void**)a3 = orig_destroying_mbc;
+	    return offset;
+	}
 	default:
 	    break;
 	}
diff --git a/erts/emulator/beam/erl_alloc_util.c b/erts/emulator/beam/erl_alloc_util.c
index 173cb091b6..8229a15824 100644
--- a/erts/emulator/beam/erl_alloc_util.c
+++ b/erts/emulator/beam/erl_alloc_util.c
@@ -6074,6 +6074,16 @@ erts_alcu_test(UWord op, UWord a1, UWord a2)
     case 0x023: return (UWord) 0;
     case 0x024: return (UWord) 0;
 #endif
+    case 0x025: /* UMEM2BLK_TEST*/
+#ifdef DEBUG
+# ifdef HARD_DEBUG
+	return (UWord)UMEM2BLK(a1-3*sizeof(UWord));
+# else
+	return (UWord)UMEM2BLK(a1-2*sizeof(UWord));
+# endif
+#else
+	return (UWord)UMEM2BLK(a1);
+#endif
 
     default:	ASSERT(0); return ~((UWord) 0);
     }
diff --git a/erts/emulator/test/alloc_SUITE.erl b/erts/emulator/test/alloc_SUITE.erl
index 7c7ddde5d4..9b0d4737b1 100644
--- a/erts/emulator/test/alloc_SUITE.erl
+++ b/erts/emulator/test/alloc_SUITE.erl
@@ -31,7 +31,8 @@
 	 rbtree/1,
 	 mseg_clear_cache/1,
 	 erts_mmap/1,
-	 cpool/1]).
+	 cpool/1,
+	 migration/1]).
 
 -export([init_per_testcase/2, end_per_testcase/2]).
 
@@ -43,7 +44,7 @@ suite() -> [{ct_hooks,[ts_install_cth]}].
 
 all() -> 
     [basic, coalesce, threads, realloc_copy, bucket_index,
-     bucket_mask, rbtree, mseg_clear_cache, erts_mmap, cpool].
+     bucket_mask, rbtree, mseg_clear_cache, erts_mmap, cpool, migration].
 
 groups() -> 
     [].
@@ -112,6 +113,8 @@ cpool(suite) -> [];
 cpool(doc) ->   [];
 cpool(Cfg) -> ?line drv_case(Cfg).
 
+migration(Cfg) -> drv_case(Cfg, concurrent, "+MZe true").
+
 erts_mmap(Config) when is_list(Config) ->
     case {?t:os_type(), is_halfword_vm()} of
 	{{unix, _}, false} ->
@@ -176,18 +179,17 @@ erts_mmap_do(Config, SCO, SCRPM, SCRFSD) ->
 %%                                                                        %%
 
 drv_case(Config) ->
-    drv_case(Config, "").
+    drv_case(Config, one_shot, "").
 
-drv_case(Config, Command) when is_list(Config),
-			       is_list(Command) ->
+drv_case(Config, Mode, NodeOpts) when is_list(Config) ->
     case ?t:os_type() of
 	{Family, _} when Family == unix; Family == win32 ->
-	    ?line {ok, Node} = start_node(Config),
+	    ?line {ok, Node} = start_node(Config, NodeOpts),
 	    ?line Self = self(),
 	    ?line Ref = make_ref(),
 	    ?line spawn_link(Node,
 			     fun () ->
-				     Res = run_drv_case(Config, Command),
+				     Res = run_drv_case(Config, Mode),
 				     Self ! {Ref, Res}
 			     end),
 	    ?line Result = receive {Ref, Rslt} -> Rslt end,
@@ -199,7 +201,7 @@ drv_case(Config, Command) when is_list(Config),
 				  | io_lib:format("~p",[SkipOs])])}
     end.
 
-run_drv_case(Config, Command) ->
+run_drv_case(Config, Mode) ->
     ?line DataDir = ?config(data_dir,Config),
     ?line CaseName = ?config(testcase,Config),
     case erl_ddll:load_driver(DataDir, CaseName) of
@@ -208,6 +210,19 @@ run_drv_case(Config, Command) ->
 	    io:format("~s\n", [erl_ddll:format_error(Error)]),
 	    ?line ?t:fail()
     end,
+
+    case Mode of
+	one_shot ->
+	    Result = one_shot(CaseName, "");
+
+	concurrent ->
+	    Result = concurrent(CaseName)
+    end,
+
+    ?line ok = erl_ddll:unload_driver(CaseName),
+    ?line Result.
+
+one_shot(CaseName, Command) ->
     ?line Port = open_port({spawn, atom_to_list(CaseName)}, []),
     ?line true = is_port(Port),
     ?line Port ! {self(), {command, Command}},
@@ -217,8 +232,45 @@ run_drv_case(Config, Command) ->
 	      {Port, closed} ->
 		  ok
 	  end,
-    ?line ok = erl_ddll:unload_driver(CaseName),
-    ?line Result.
+    Result.
+
+
+many_shot(CaseName, Command) ->
+    ?line Port = open_port({spawn, atom_to_list(CaseName)}, []),
+    ?line true = is_port(Port),
+    Result = repeat_while(fun() ->
+				  ?line Port ! {self(), {command, Command}},
+				  receive_drv_result(Port, CaseName) =:= continue
+			  end),
+    ?line Port ! {self(), close},
+    ?line receive
+	      {Port, closed} ->
+		  ok
+	  end,
+    Result.
+
+concurrent(CaseName) ->
+    one_shot(CaseName, "init"),
+    PRs = lists:map(fun(I) -> spawn_opt(fun() ->
+						many_shot(CaseName, "")
+					end,
+				       [monitor, {scheduler,I}])
+		    end,
+		    lists:seq(1, erlang:system_info(schedulers))),
+    lists:foreach(fun({Pid,Ref}) ->
+			  receive {'DOWN', Ref, process, Pid, Reason} ->
+				  Reason
+			  end
+		  end,
+		  PRs),
+    ok.
+
+repeat_while(Fun) ->
+    io:format("~p calls fun\n", [self()]),
+    case Fun() of
+	true -> repeat_while(Fun);
+	false -> ok
+    end.
 
 receive_drv_result(Port, CaseName) ->
     ?line receive
@@ -236,11 +288,11 @@ receive_drv_result(Port, CaseName) ->
 	      {succeeded, Port, CaseName, ""} ->
 		  ?line succeeded;
 	      {succeeded, Port, CaseName, Comment} ->
-		  ?line {comment, Comment}
+		  ?line {comment, Comment};
+	      continue ->
+		  continue
 	  end.
 
-start_node(Config) ->
-    start_node(Config, []).
 start_node(Config, Opts) when is_list(Config), is_list(Opts) ->
     Pa = filename:dirname(code:which(?MODULE)),
     Name = list_to_atom(atom_to_list(?MODULE)
diff --git a/erts/emulator/test/alloc_SUITE_data/Makefile.src b/erts/emulator/test/alloc_SUITE_data/Makefile.src
index a441fe946b..e31de54e1b 100644
--- a/erts/emulator/test/alloc_SUITE_data/Makefile.src
+++ b/erts/emulator/test/alloc_SUITE_data/Makefile.src
@@ -25,7 +25,8 @@ TEST_DRVS =	basic@dll@		\
 		bucket_mask@dll@	\
 		rbtree@dll@		\
 		mseg_clear_cache@dll@	\
-		cpool@dll@
+		cpool@dll@		\
+		migration@dll@
 
 CC = @CC@
 LD = @LD@
diff --git a/erts/emulator/test/alloc_SUITE_data/allocator_test.h b/erts/emulator/test/alloc_SUITE_data/allocator_test.h
index a7f12c6ca8..bfd0bb3094 100644
--- a/erts/emulator/test/alloc_SUITE_data/allocator_test.h
+++ b/erts/emulator/test/alloc_SUITE_data/allocator_test.h
@@ -85,6 +85,7 @@ typedef void* erts_cond;
 #define CPOOL_DELETE(A,B)	((Carrier_t *)	ALC_TEST2(0x022, (A), (B)))
 #define CPOOL_IS_EMPTY(A)	((int)		ALC_TEST1(0x023, (A)))
 #define CPOOL_IS_IN_POOL(A,B)	((int)		ALC_TEST2(0x024, (A), (B)))
+#define UMEM2BLK_TEST(P)	((Block_t*)	ALC_TEST1(0x025, (P)))
 
 /* From erl_goodfit_alloc.c */
 #define BKT_IX(A, S)		((Ulong)	ALC_TEST2(0x100, (A), (S)))
@@ -144,5 +145,6 @@ typedef void* erts_cond;
 #define IS_SMP_ENABLED		((int)		ALC_TEST0(0xf13))
 #define ALLOC_TEST(S)		((void*)	ALC_TEST1(0xf14, (S)))
 #define FREE_TEST(P)		((void)		ALC_TEST1(0xf15, (P)))
+#define SET_TEST_MBC_USER_HEADER(SZ,CMBC,DMBC) ((int)ALC_TEST3(0xf16, (SZ), (CMBC), (DMBC)))
 
 #endif
diff --git a/erts/emulator/test/alloc_SUITE_data/migration.c b/erts/emulator/test/alloc_SUITE_data/migration.c
new file mode 100644
index 0000000000..dd58a0d3dd
--- /dev/null
+++ b/erts/emulator/test/alloc_SUITE_data/migration.c
@@ -0,0 +1,229 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2014. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Erlang Public License,
+ * Version 1.1, (the "License"); you may not use this file except in
+ * compliance with the License. You should have received a copy of the
+ * Erlang Public License along with this software. If not, it can be
+ * retrieved online at http://www.erlang.org/.
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/*
+ * Test the carrier migration logic
+ */
+
+#ifndef __WIN32__
+#include <sys/types.h>
+#include <unistd.h>
+#include <errno.h>
+#endif
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include "testcase_driver.h"
+#include "allocator_test.h"
+
+#define FATAL_ASSERT(A)						\
+    ((void) ((A)						\
+	     ? 1						\
+	     : (fatal_assert_failed(#A,				\
+				    (char *) __FILE__,		\
+				    __LINE__),			\
+		0)))
+
+static void
+fatal_assert_failed(char* expr, char* file, int line)
+{
+    fflush(stdout);
+    fprintf(stderr, "%s:%d: Assertion failed: %s\n",
+	    file, line, expr);
+    fflush(stderr);
+    abort();
+}
+
+
+char *
+testcase_name(void)
+{
+    return "migration";
+}
+
+void
+testcase_cleanup(TestCaseState_t *tcs)
+{
+}
+
+#define MAX_BLOCK_PER_THR 100
+#define MAX_ROUNDS 10
+
+typedef struct MyBlock_ {
+    struct MyBlock_* next;
+    struct MyBlock_** prevp;
+} MyBlock;
+
+typedef struct {
+    MyBlock* blockv[MAX_BLOCK_PER_THR];
+    enum { GROWING, SHRINKING, CLEANUP, DONE } phase;
+    int ix;
+    int round;
+} MigrationState;
+
+typedef struct {
+    ErlDrvMutex* mtx;
+    int nblocks;
+    MyBlock* first;
+} MyCrrInfo;
+
+
+static int crr_info_offset = -1;
+static void (*orig_create_mbc_fn)(Allctr_t *allctr, Carrier_t *carrier);
+static void (*orig_destroying_mbc_fn)(Allctr_t *allctr, Carrier_t *carrier);
+
+static void my_creating_mbc(Allctr_t *allctr, Carrier_t *carrier)
+{
+    MyCrrInfo* mci = (MyCrrInfo*) ((char*)carrier + crr_info_offset);
+    if (orig_create_mbc_fn)
+	orig_create_mbc_fn(allctr, carrier);
+
+    mci->mtx = erl_drv_mutex_create("alloc_SUITE.migration");
+    mci->nblocks = 0;
+    mci->first = NULL;
+}
+
+static void my_destroying_mbc(Allctr_t *allctr, Carrier_t *carrier)
+{
+    MyCrrInfo* mci = (MyCrrInfo*) ((char*)carrier + crr_info_offset);
+
+    FATAL_ASSERT(mci->nblocks == 0);
+    FATAL_ASSERT(mci->first == NULL);
+    erl_drv_mutex_destroy(mci->mtx);
+
+    if (orig_destroying_mbc_fn)
+	orig_destroying_mbc_fn(allctr, carrier);
+}
+
+
+static void setup(TestCaseState_t* tcs)
+{
+    void* creating_mbc_arg = (void*)my_creating_mbc;
+    void* destroying_mbc_arg = (void*)my_destroying_mbc;
+    crr_info_offset = SET_TEST_MBC_USER_HEADER(sizeof(MyCrrInfo),
+					       &creating_mbc_arg,
+					       &destroying_mbc_arg);
+    ASSERT(tcs, crr_info_offset >= 0);
+    orig_create_mbc_fn = creating_mbc_arg;
+    orig_destroying_mbc_fn = destroying_mbc_arg;
+}
+
+static void add_block(MyBlock* p)
+{
+    MyCrrInfo* mci = (MyCrrInfo*)((char*)BLK_TO_MBC(UMEM2BLK_TEST(p)) + crr_info_offset);
+
+    erl_drv_mutex_lock(mci->mtx);
+    mci->nblocks++;
+    p->next = mci->first;
+    p->prevp = &mci->first;
+    mci->first = p;
+    if (p->next)
+	p->next->prevp = &p->next;
+    erl_drv_mutex_unlock(mci->mtx);
+}
+
+static void remove_block(MyBlock* p)
+{
+    MyCrrInfo* mci = (MyCrrInfo*)((char*)BLK_TO_MBC(UMEM2BLK_TEST(p)) + crr_info_offset);
+
+    erl_drv_mutex_lock(mci->mtx);
+    mci->nblocks--;
+    if (p->next)
+	p->next->prevp = p->prevp;
+    *p->prevp = p->next;
+    erl_drv_mutex_unlock(mci->mtx);
+}
+
+void
+testcase_run(TestCaseState_t *tcs)
+{
+    MigrationState* state = (MigrationState*) tcs->extra;
+
+    if (tcs->command_len == 4
+	&& memcmp(tcs->command, "init", tcs->command_len) == 0) {
+	setup(tcs);
+	return;
+    }
+
+    if (!tcs->extra) {
+	if (!IS_SMP_ENABLED)
+	    testcase_skipped(tcs, "No SMP support");
+
+	tcs->extra = driver_alloc(sizeof(MigrationState));
+	state = (MigrationState*) tcs->extra;
+	memset(state->blockv, 0, sizeof(state->blockv));
+	state->phase = GROWING;
+	state->ix = 0;
+	state->round = 0;
+    }
+
+    switch (state->phase) {
+    case GROWING: {
+	MyBlock* p;
+	FATAL_ASSERT(!state->blockv[state->ix]);
+	p = ALLOC_TEST((1 << 18) / 5);
+	FATAL_ASSERT(p);
+	add_block(p);
+	state->blockv[state->ix] = p;
+	do {
+	    if (++state->ix >= MAX_BLOCK_PER_THR) {
+		state->phase = SHRINKING;
+		state->ix = 0;
+		break;
+	    }
+	} while (state->blockv[state->ix] != NULL);
+	break;
+    }
+    case SHRINKING:
+	FATAL_ASSERT(state->blockv[state->ix]);
+	remove_block(state->blockv[state->ix]);
+	FREE_TEST(state->blockv[state->ix]);
+	state->blockv[state->ix] = NULL;
+
+	state->ix += 1 + ((state->ix % 3) == 0);
+	if (state->ix >= MAX_BLOCK_PER_THR) {
+	    if (++state->round >= MAX_ROUNDS) {
+		state->phase = CLEANUP;
+	    } else {
+		state->phase = GROWING;
+	    }
+	    state->ix = 0;
+	}
+	break;
+
+    case CLEANUP:
+	if (state->blockv[state->ix]) {
+	    remove_block(state->blockv[state->ix]);
+	    FREE_TEST(state->blockv[state->ix]);
+	}
+	if (++state->ix >= MAX_BLOCK_PER_THR)
+	    state->phase = DONE;
+	break;
+
+    default:
+	FATAL_ASSERT(!"Invalid phase");
+    }
+
+    if (state->phase == DONE) {
+	driver_free(tcs->extra);
+	tcs->extra = NULL;
+    }
+    else
+	testcase_continue(tcs);
+}
diff --git a/erts/emulator/test/alloc_SUITE_data/testcase_driver.c b/erts/emulator/test/alloc_SUITE_data/testcase_driver.c
index bc674c56b7..d04eb1bcb0 100644
--- a/erts/emulator/test/alloc_SUITE_data/testcase_driver.c
+++ b/erts/emulator/test/alloc_SUITE_data/testcase_driver.c
@@ -39,6 +39,7 @@
 #define TESTCASE_FAILED		0
 #define TESTCASE_SKIPPED	1
 #define TESTCASE_SUCCEEDED	2
+#define TESTCASE_CONTINUE	3
 
 typedef struct {
     TestCaseState_t visible;
@@ -129,6 +130,12 @@ testcase_drv_run(ErlDrvData drv_data, char *buf, ErlDrvSizeT len)
     }
 
     switch (itcs->result) {
+    case TESTCASE_CONTINUE:
+	msg[0] = ERL_DRV_ATOM;
+	msg[1] = driver_mk_atom("continue");
+	erl_drv_output_term(itcs->port_id, msg, 2);
+	return;
+
     case TESTCASE_SUCCEEDED:
 	result_atom = driver_mk_atom("succeeded");
 	break;
@@ -239,6 +246,13 @@ void testcase_skipped(TestCaseState_t *tcs, char *frmt, ...)
     longjmp(itcs->done_jmp_buf, 1);
 }
 
+void testcase_continue(TestCaseState_t *tcs)
+{
+    InternalTestCaseState_t *itcs = (InternalTestCaseState_t *) tcs;
+    itcs->result = TESTCASE_CONTINUE;
+    longjmp(itcs->done_jmp_buf, 1);
+}
+
 void testcase_failed(TestCaseState_t *tcs, char *frmt, ...)
 {
     InternalTestCaseState_t *itcs = (InternalTestCaseState_t *) tcs;
diff --git a/erts/emulator/test/alloc_SUITE_data/testcase_driver.h b/erts/emulator/test/alloc_SUITE_data/testcase_driver.h
index 5d17eaec64..5d439735b7 100644
--- a/erts/emulator/test/alloc_SUITE_data/testcase_driver.h
+++ b/erts/emulator/test/alloc_SUITE_data/testcase_driver.h
@@ -37,6 +37,7 @@ typedef struct {
 void testcase_printf(TestCaseState_t *tcs, char *frmt, ...);
 void testcase_succeeded(TestCaseState_t *tcs, char *frmt, ...);
 void testcase_skipped(TestCaseState_t *tcs, char *frmt, ...);
+void testcase_continue(TestCaseState_t *tcs);
 void testcase_failed(TestCaseState_t *tcs, char *frmt, ...);
 int testcase_assertion_failed(TestCaseState_t *tcs, char *file, int line,
 			      char *assertion);
-- 
cgit v1.2.3