4
4
% %
5
5
% % It spawns a pool of workers as big as online schedulers. When starting a new user, as the user is
6
6
% % identified by ID, a worker will be chosen for this user based on its ID
7
- % % (see get_sup_for_user_id /1).
7
+ % % (see gen_sup_from_userid /1).
8
8
% %
9
9
% % The currently running number of users is stored in an atomic that all workers update and the
10
10
% % controller can read.
16
16
-export ([start_link /0 , init /1 ]).
17
17
18
18
% % API
19
- -export ([incr_no_of_users /1 , decr_no_of_users /1 , count_no_of_users /0 ,
20
- start_child /3 , stop_child /2 , start_children /3 , stop_children /2 , terminate_all_children /0 ]).
21
-
19
+ -export ([handle_up_user /3 , handle_down_user /2 , count_no_of_users /0 ]).
20
+ -export ([start_children /3 , stop_child /2 , stop_children /2 , terminate_all_children /0 ]).
22
21
-export ([distribute /2 , get_all_children /0 ]).
23
22
24
23
-type count () :: non_neg_integer ().
34
33
sups_count :: pos_integer ()
35
34
}).
36
35
36
+ -define (SUPERVISOR , amoc_users_sup ).
37
+ -define (STORAGE , amoc_users_sup_storage ).
38
+ -define (TABLE , amoc_users_sup_table ).
39
+
37
40
% % Supervisor
38
41
39
42
% % @private
40
43
-spec start_link () -> supervisor :startlink_ret ().
41
44
start_link () ->
42
- Ret = supervisor :start_link ({local , ? MODULE }, ? MODULE , no_args ),
43
- UserSups = supervisor :which_children (? MODULE ),
44
- IndexedSupsUnsorted = [ {Pid , N } || {{amoc_users_worker_sup , N }, Pid , _ , _ } <- UserSups ],
45
+ Ret = supervisor :start_link ({local , ? SUPERVISOR }, ? MODULE , no_args ),
46
+ UserSups = supervisor :which_children (? SUPERVISOR ),
47
+ IndexedSupsUnsorted = [ {Pid , N } || {{amoc_users_worker_sup , N }, Pid , _ , _ } <- UserSups ,
48
+ is_integer (N ), is_pid (Pid )],
45
49
IndexedSups = lists :keysort (2 , IndexedSupsUnsorted ),
46
50
UserSupPidsTuple = list_to_tuple ([ Pid || {Pid , _ } <- IndexedSups ]),
47
51
SupCount = tuple_size (UserSupPidsTuple ),
48
52
Atomics = atomics :new (1 + SupCount , [{signed , false }]),
49
53
Storage = # storage {user_count = Atomics , sups = UserSupPidsTuple ,
50
54
sups_indexed = IndexedSups , sups_count = SupCount },
51
- persistent_term :put (? MODULE , Storage ),
55
+ persistent_term :put (? STORAGE , Storage ),
52
56
Ret .
53
57
54
58
% % @private
55
59
-spec init (no_args ) -> {ok , {supervisor :sup_flags (), [supervisor :child_spec ()]}}.
56
60
init (no_args ) ->
61
+ EtsOpts = [ordered_set , public , named_table ,
62
+ {read_concurrency , true }, {write_concurrency , auto }],
63
+ _Table = ets :new (? TABLE , EtsOpts ),
57
64
Specs = [
58
65
#{
59
66
id => {amoc_users_worker_sup , N },
@@ -73,18 +80,20 @@ indexes() ->
73
80
% % API
74
81
-spec count_no_of_users () -> count ().
75
82
count_no_of_users () ->
76
- # storage {user_count = Atomics } = persistent_term :get (? MODULE ),
83
+ # storage {user_count = Atomics } = persistent_term :get (? STORAGE ),
77
84
atomics :get (Atomics , 1 ).
78
85
79
- -spec incr_no_of_users (non_neg_integer ()) -> any ().
80
- incr_no_of_users (SupNum ) when SupNum > 1 ->
81
- # storage {user_count = Atomics } = persistent_term :get (? MODULE ),
86
+ -spec handle_up_user (non_neg_integer (), pid (), amoc_scenario :user_id ()) -> any ().
87
+ handle_up_user (SupNum , Pid , Id ) when SupNum > 1 ->
88
+ ets :insert (? TABLE , {Pid , Id }),
89
+ # storage {user_count = Atomics } = persistent_term :get (? STORAGE ),
82
90
atomics :add (Atomics , SupNum , 1 ),
83
91
atomics :add (Atomics , 1 , 1 ).
84
92
85
- -spec decr_no_of_users (non_neg_integer ()) -> ok .
86
- decr_no_of_users (SupNum ) when SupNum > 1 ->
87
- # storage {user_count = Atomics } = persistent_term :get (? MODULE ),
93
+ -spec handle_down_user (non_neg_integer (), pid ()) -> ok .
94
+ handle_down_user (SupNum , Pid ) when SupNum > 1 ->
95
+ ets :delete (? TABLE , Pid ),
96
+ # storage {user_count = Atomics } = persistent_term :get (? STORAGE ),
88
97
atomics :sub (Atomics , SupNum , 1 ),
89
98
case atomics :sub_get (Atomics , 1 , 1 ) of
90
99
0 ->
@@ -93,24 +102,22 @@ decr_no_of_users(SupNum) when SupNum > 1 ->
93
102
ok
94
103
end .
95
104
96
- -spec start_child (amoc :scenario (), amoc_scenario :user_id (), any ()) -> ok .
97
- start_child (Scenario , Id , ScenarioState ) ->
98
- Sup = get_sup_for_user_id (Id ),
99
- amoc_users_worker_sup :start_child (Sup , Scenario , Id , ScenarioState ).
100
-
101
105
-spec stop_child (pid (), boolean ()) -> ok .
102
106
stop_child (Pid , Force ) ->
103
- amoc_users_worker_sup :stop_child (Pid , Force ).
107
+ case ets :lookup (? TABLE , Pid ) of
108
+ [Object ] ->
109
+ Sup = gen_sup_from_userid (Object ),
110
+ amoc_users_worker_sup :stop_children (Sup , [Pid ], Force );
111
+ _ ->
112
+ ok
113
+ end .
104
114
105
115
% % Group all children based on ID to their respective worker supervisor and cast a request with each
106
116
% % group at once. This way we reduce the number of casts to each worker to always one, instead of
107
117
% % depending on the number of users.
108
118
-spec start_children (amoc :scenario (), [amoc_scenario :user_id ()], any ()) -> ok .
109
119
start_children (Scenario , UserIds , ScenarioState ) ->
110
- State = persistent_term :get (? MODULE ),
111
- # storage {sups = Supervisors , sups_indexed = IndexedSups , sups_count = SupCount } = State ,
112
- Acc = maps :from_list ([ {Sup , []} || {Sup , _ } <- IndexedSups ]),
113
- Assignments = assign_users_to_sups (SupCount , Supervisors , UserIds , Acc ),
120
+ Assignments = maps :groups_from_list (fun gen_sup_from_userid /1 , UserIds ),
114
121
CastFun = fun (Sup , Users ) ->
115
122
amoc_users_worker_sup :start_children (Sup , Scenario , Users , ScenarioState )
116
123
end ,
@@ -120,47 +127,53 @@ start_children(Scenario, UserIds, ScenarioState) ->
120
127
% % in order to load-balance the request among all workers.
121
128
-spec stop_children (non_neg_integer (), boolean ()) -> non_neg_integer ().
122
129
stop_children (Count , Force ) ->
123
- {CountRemove , Assignments } = assign_counts (Count ),
124
- [ amoc_users_worker_sup :stop_children (Sup , Int , Force ) || {Sup , Int } <- Assignments ],
125
- CountRemove .
130
+ Users = case ets :match_object (? TABLE , '$1' , Count ) of
131
+ '$end_of_table' ->
132
+ [];
133
+ {Objects , _ } ->
134
+ Objects
135
+ end ,
136
+ stop_children_assignments (Users , Force ),
137
+ length (Users ).
126
138
127
139
-spec get_all_children () -> [{pid (), amoc_scenario :user_id ()}].
128
140
get_all_children () ->
129
- # storage {sups_indexed = IndexedSups } = persistent_term :get (? MODULE ),
130
- All = [ amoc_users_worker_sup :get_all_children (Sup ) || {Sup , _ } <- IndexedSups ],
131
- lists :flatten (All ).
141
+ ets :tab2list (? TABLE ).
132
142
133
143
-spec terminate_all_children () -> any ().
134
144
terminate_all_children () ->
135
- # storage {sups_indexed = IndexedSups } = persistent_term :get (? MODULE ),
136
- [ amoc_users_worker_sup :terminate_all_children (Sup ) || {Sup , _ } <- IndexedSups ].
145
+ Match = ets :match_object (? TABLE , '$1' , 500 ),
146
+ do_terminate_all_my_children (Match ).
147
+
148
+ -spec stop_children_assignments ([{pid (), amoc_scenario :user_id ()}], boolean ()) -> ok .
149
+ stop_children_assignments (Users , Force ) ->
150
+ Assignments = maps :groups_from_list (fun gen_sup_from_userid /1 , fun get_pid /1 , Users ),
151
+ CastFun = fun (Sup , Assignment ) ->
152
+ amoc_users_worker_sup :stop_children (Sup , Assignment , Force )
153
+ end ,
154
+ maps :foreach (CastFun , Assignments ).
155
+
156
+ % % ets:continuation/0 type is unfortunately not exported from the ets module.
157
+ -spec do_terminate_all_my_children ({[tuple ()], dynamic ()} | '$end_of_table' ) -> ok .
158
+ do_terminate_all_my_children ({Users , Continuation }) ->
159
+ stop_children_assignments (Users , true ),
160
+ Match = ets :match_object (Continuation ),
161
+ do_terminate_all_my_children (Match );
162
+ do_terminate_all_my_children ('$end_of_table' ) ->
163
+ ok .
137
164
138
165
% % Helpers
139
- -spec get_sup_for_user_id (amoc_scenario :user_id ()) -> pid ().
140
- get_sup_for_user_id (Id ) ->
141
- # storage {sups = Supervisors , sups_count = SupCount } = persistent_term :get (? MODULE ),
166
+ -spec gen_sup_from_userid ({pid (), amoc_scenario :user_id ()} | amoc_scenario :user_id ()) -> pid ().
167
+ gen_sup_from_userid ({_Pid , Id }) ->
168
+ gen_sup_from_userid (Id );
169
+ gen_sup_from_userid (Id ) ->
170
+ # storage {sups = Supervisors , sups_count = SupCount } = persistent_term :get (? STORAGE ),
142
171
Index = erlang :phash2 (Id , SupCount ) + 1 ,
143
172
element (Index , Supervisors ).
144
173
145
- % % assign which users each worker will be requested to add
146
- -spec assign_users_to_sups (pos_integer (), tuple (), [amoc_scenario :user_id ()], Acc ) ->
147
- Acc when Acc :: #{pid () := [amoc_scenario :user_id ()]}.
148
- assign_users_to_sups (SupCount , Supervisors , [Id | Ids ], Acc ) ->
149
- Index = erlang :phash2 (Id , SupCount ) + 1 ,
150
- ChosenSup = element (Index , Supervisors ),
151
- Vs = maps :get (ChosenSup , Acc ),
152
- NewAcc = Acc #{ChosenSup := [Id | Vs ]},
153
- assign_users_to_sups (SupCount , Supervisors , Ids , NewAcc );
154
- assign_users_to_sups (_ , _ , [], Acc ) ->
155
- Acc .
156
-
157
- % % assign how many users each worker will be requested to remove,
158
- % % taking care of the fact that worker might not have enough users.
159
- -spec assign_counts (count ()) -> {count (), assignment ()}.
160
- assign_counts (Total ) ->
161
- # storage {user_count = Atomics , sups_indexed = Indexed } = persistent_term :get (? MODULE ),
162
- SupervisorsWithCounts = [ {Sup , atomics :get (Atomics , SupPos )} || {Sup , SupPos } <- Indexed ],
163
- distribute (Total , SupervisorsWithCounts ).
174
+ -spec get_pid ({pid (), amoc_scenario :user_id ()}) -> pid ().
175
+ get_pid ({Pid , _ }) ->
176
+ Pid .
164
177
165
178
-spec distribute (count (), assignment ()) -> {count (), assignment ()}.
166
179
distribute (Total , SupervisorsWithCounts ) ->
0 commit comments