From 08972f01608fc28adfab5a3334eb0630a1945dc2 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Thu, 22 Mar 2018 19:04:34 +0900 Subject: [PATCH] blah --- zomp/lib/otpr-zx/0.1.0/src/.zx_daemon.erl.swp | Bin 0 -> 147456 bytes zomp/lib/otpr-zx/0.1.0/src/zx_conn.erl | 106 +++++ zomp/lib/otpr-zx/0.1.0/src/zx_daemon.erl | 402 +++++++++--------- 3 files changed, 296 insertions(+), 212 deletions(-) create mode 100644 zomp/lib/otpr-zx/0.1.0/src/.zx_daemon.erl.swp diff --git a/zomp/lib/otpr-zx/0.1.0/src/.zx_daemon.erl.swp b/zomp/lib/otpr-zx/0.1.0/src/.zx_daemon.erl.swp new file mode 100644 index 0000000000000000000000000000000000000000..1b7e5a7fbf601a9917d534322d8b2b3d6cff5b7f GIT binary patch literal 147456 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 z�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 literal 0 HcmV?d00001 diff --git a/zomp/lib/otpr-zx/0.1.0/src/zx_conn.erl b/zomp/lib/otpr-zx/0.1.0/src/zx_conn.erl index 4cefbad..ec6644f 100644 --- a/zomp/lib/otpr-zx/0.1.0/src/zx_conn.erl +++ b/zomp/lib/otpr-zx/0.1.0/src/zx_conn.erl @@ -303,3 +303,109 @@ handle_timeout(Socket) -> terminate() -> exit(normal). + + + +%-spec do_query_latest(Object, State) -> {Result, NewState} +% when Object :: zx:package() | zx:package_id(), +% State :: state(), +% Result :: {ok, zx:version()} +% | {error, Reason}, +% Reason :: bad_realm +% | bad_package +% | bad_version, +% NewState :: state(). +%% @private +%% Queries a zomp realm for the latest version of a package or package +%% version (complete or incomplete version number). +% +%do_query_latest(Socket, {Realm, Name}) -> +% ok = zx_net:send(Socket, {latest, Realm, Name}), +% receive +% {tcp, Socket, Bin} -> binary_to_term(Bin) +% after 5000 -> {error, timeout} +% end; +%do_query_latest(Socket, {Realm, Name, Version}) -> +% ok = zx_net:send(Socket, {latest, Realm, Name, Version}), +% receive +% {tcp, Socket, Bin} -> binary_to_term(Bin) +% after 5000 -> {error, timeout} +% end. + + +%-spec do_fetch(PackageIDs, State) -> NewState +% when PackageIDs :: [zx:package_id()], +% State :: state(), +% NewState :: state(), +% Result :: ok +% | {error, Reason}, +% Reason :: bad_realm +% | bad_package +% | bad_version +% | network. +%% @private +%% +% +%do_fetch(PackageIDs, State) -> +% FIXME: Need to create a job queue divided by realm and dispatched to connectors, +% and cleared from the master pending queue kept here by the daemon as the +% workers succeed. Basic task queue management stuff... which never existed +% in ZX before... grrr... +% case scrub(PackageIDs) of +% [] -> +% ok; +% Needed -> +% Partitioned = partition_by_realm(Needed), +% EnsureDeps = +% fun({Realm, Packages}) -> +% ok = zx_conn:queue_package(Pid, Realm, Packages), +% log(info, "Disconnecting from realm: ~ts", [Realm]) +% end, +% lists:foreach(EnsureDeps, Partitioned) +% end. +% +% +%partition_by_realm(PackageIDs) -> +% PartitionMap = lists:foldl(fun partition_by_realm/2, #{}, PackageIDs), +% maps:to_list(PartitionMap). +% +% +%partition_by_realm({R, P, V}, M) -> +% maps:update_with(R, fun(Ps) -> [{P, V} | Ps] end, [{P, V}], M). +% +% +%ensure_deps(_, _, []) -> +% ok; +%ensure_deps(Socket, Realm, [{Name, Version} | Rest]) -> +% ok = ensure_dep(Socket, {Realm, Name, Version}), +% ensure_deps(Socket, Realm, Rest). +% +% +%-spec ensure_dep(gen_tcp:socket(), package_id()) -> ok | no_return(). +%% @private +%% Given an PackageID as an argument, check whether its package file exists in the +%% system cache, and if not download it. Should return `ok' whenever the file is +%% sourced, but exit with an error if it cannot locate or acquire the package. +% +%ensure_dep(Socket, PackageID) -> +% ZrpFile = filename:join("zrp", namify_zrp(PackageID)), +% ok = +% case filelib:is_regular(ZrpFile) of +% true -> ok; +% false -> fetch(Socket, PackageID) +% end, +% ok = install(PackageID), +% build(PackageID). +% +% +%-spec scrub(Deps) -> Scrubbed +% when Deps :: [package_id()], +% Scrubbed :: [package_id()]. +%% @private +%% Take a list of dependencies and return a list of dependencies that are not yet +%% installed on the system. +% +%scrub([]) -> +% []; +%scrub(Deps) -> +% lists:filter(fun(PackageID) -> not zx_lib:installed(PackageID) end, Deps). diff --git a/zomp/lib/otpr-zx/0.1.0/src/zx_daemon.erl b/zomp/lib/otpr-zx/0.1.0/src/zx_daemon.erl index caf10fe..b078f0b 100644 --- a/zomp/lib/otpr-zx/0.1.0/src/zx_daemon.erl +++ b/zomp/lib/otpr-zx/0.1.0/src/zx_daemon.erl @@ -1,30 +1,31 @@ %%% @doc %%% ZX Daemon %%% -%%% Resident execution daemon and runtime interface to Zomp. +%%% Resident task daemon and runtime interface to Zomp. %%% %%% The daemon resides in the background once started and awaits query requests and -%%% subscriptions from from other processes. The daemon is only capable of handling +%%% subscriptions from other processes. The daemon is only capable of handling %%% unprivileged (user) actions. %%% %%% %%% Discrete state and local abstract data types %%% %%% The daemon must keep track of requestors, subscribers, and zx_conn processes by -%%% using monitors, and because the various types of clients are found in different -%%% locations the monitors are maintained in a data type called monitor_index(), +%%% using monitors. Because these various types of clients are found in different +%%% structures the monitors are maintained in a data type called monitor_index(), %%% shortened to "mx" throughout the module. This structure is treated as an opaque %%% data type and is handled by a set of functions defined toward the end of the module %%% as mx_*/N. %%% -%%% Node connections (cx_conn processes) must also be tracked for status and realm +%%% Node connections (zx_conn processes) must also be tracked for status and realm %%% availability. This is done using a type called conn_index(), shortened to "cx" %%% throughout the module. conn_index() is treated as an abstract, opaque datatype %%% throughout the module, and is handled via a set of cx_*/N functions (after the %%% mx_*/N section). %%% %%% Do NOT directly access data within these structures, use (or write) an accessor -%%% function that does what you want. +%%% function that does what you want. Accessor functions MUST be pure with the sole +%%% exception of the mx_* functions that create and destroy monitors. %%% %%% %%% Connection handling @@ -40,7 +41,7 @@ %%% determine what realms must be available and what cached Zomp nodes it is aware of. %%% It populates the CX (conn_index(), mentioned above) with realm config and host %%% cache data, and then immediately initiates three connection attempts to cached -%%% nodes for each realm configured (if possible; see init_connections/0). +%%% nodes for each realm configured if possible (see init_connections/0). %%% %%% Once connection attempts have been initiated the daemon waits in receive for %%% either a connection report (success or failure) or an action request from @@ -90,6 +91,44 @@ %%% a blocking way, establishing its own receive timeouts or implementing either an %%% asynchronous or synchronous interface library atop zx_daemon interface function, %%% but leaving zx_daemon and zx_conn alone to work asynchronously with one another. +%%% +%%% +%%% Race Avoidance +%%% +%%% Each runtime can only have one zx_daemon alive at a time, and each system can +%%% only have one zx_daemon directly performing actions at a time. This is to prevent +%%% problems where multiple zx instances are running at the same time using the same +%%% home directory and might clash with one another (overwriting each other's data, +%%% corrupting package or key files, etc.). OTP makes running a single registered +%%% process simple within a single runtime, but there is no standard cross-platform +%%% method for ensuring a given process is the only one of its type in a given scope +%%% within a host system. +%%% +%%% When zx starts the daemon will attempt an exclusive write to a lock file called +%%% $ZOMP_HOME/zomp.lock using file:open(LockFile, [exclusive]), writing a system +%%% timestamp. If the write succeeds then the daemon knows it is the master for the +%%% system and will begin initiating connections as described above as well as open a +%%% local socket to listen for other zx instances which will need to proxy their own +%%% actions through the master. Once the socket is open, the lock file is updated with +%%% the local port number. If the write fails then the file is read and if a port +%%% number is indicated then the daemon connects to the master zx_daemon and proxies +%%% its requests through it. If no port number exists then the daemon waits 5 seconds, +%%% checks again, and if there is still no port number then it checks whether the +%%% timestamp is more than 5 seconds old or in the future. If the timestamp is more +%%% than 5 seconds old or in the future then the file is deleted and the process +%%% of master identification starts again. +%%% +%%% If a master daemon's runtime is shutting down it will designate its oldest peer +%%% daemon connection as the new master. At that point the new master will open a port +%%% and rewrite the lock file. Once written the old master will drop all its node +%%% connections and dequeue all current requests, then pass a local redirect message +%%% to the subordinate daemons telling them the new port to which they should connect. +%%% +%%% Even if there is considrable churn within a system from, for example, scripted +%%% initiation of several small utilities that have never been executed before, the +%%% longest-living daemon should always become the master. This is not the most +%%% efficient procedure, but it is the easiest to understand and debug across various +%%% platforms. %%% @end -module(zx_daemon). @@ -274,12 +313,6 @@ %% the filesystem. This step allows running development code from any location in %% the filesystem against installed dependencies without requiring any magical %% references. -%% -%% This call blocks specifically so that we can be certain that the target application -%% cannot be started before the impact of this call has taken full effect. It cannot -%% be known whether the very first thing the target application will do is send this -%% process an async message. That implies that this should only ever be called once, -%% by the launching process (which normally terminates shortly thereafter). pass_meta(Meta, Dir, ArgV) -> gen_server:cast(?MODULE, {pass_meta, Meta, Dir, ArgV}). @@ -288,11 +321,11 @@ pass_meta(Meta, Dir, ArgV) -> -spec subscribe(Package) -> ok when Package :: zx:package(). %% @doc -%% Subscribe to update notifications for a for a particular package. +%% Subscribe to update notifications for a for a package. %% The caller will receive update notifications of type `sub_message()' as Erlang %% messages whenever an update occurs. %% Crashes the caller if the Realm or Name of the Package argument are illegal -%% `zx:lower0_9' strings. +%% `zx:lower0_9()' strings. subscribe(Package = {Realm, Name}) -> true = zx_lib:valid_lower0_9(Realm), @@ -558,6 +591,8 @@ start_link() -> -spec init(none) -> {ok, state()}. +%% @private +%% TODO: Implement lockfile checking and master lock acquisition. init(none) -> Blank = blank_state(), @@ -674,7 +709,8 @@ handle_cast({result, Ref, Result}, State) -> {noreply, NewState}; handle_cast({notify, Conn, Package, Update}, State) -> ok = do_notify(Conn, Package, Update, State), - {noreply, State}; + NewState = eval_queue(State), + {noreply, NewState}; handle_cast(stop, State) -> {stop, normal, State}; handle_cast(Unexpected, State) -> @@ -704,6 +740,7 @@ code_change(_, State, _) -> %% @private %% gen_server callback to handle shutdown/cleanup tasks on receipt of a clean %% termination request. +%% TODO: Implement new master selection, dequeuing and request queue passing. terminate(normal, #s{cx = CX}) -> ok = log(info, "zx_daemon shutting down..."), @@ -778,7 +815,7 @@ do_request(Requestor, Action, State = #s{id = ID, actions = Actions}) -> State :: state(), NewState :: state(). %% @private -%% Receive a report from a connection process, and update the connection index and +%% Receive a report from a connection process, update the connection index and %% possibly retry connections. do_report(Conn, {connected, Realms}, State = #s{mx = MX, cx = CX}) -> @@ -788,7 +825,7 @@ do_report(Conn, {connected, Realms}, State = #s{mx = MX, cx = CX}) -> {assigned, NextCX} -> {NextMX, NextCX}; {unassigned, NextCX} -> - {ok, ScrubbedMX} = mx_del_monitor(Conn, conn, NextMX), + ScrubbedMX = mx_del_monitor(Conn, conn, NextMX), ok = zx_conn:stop(Conn), {ScrubbedMX, NextCX} end, @@ -796,9 +833,9 @@ do_report(Conn, {connected, Realms}, State = #s{mx = MX, cx = CX}) -> do_report(Conn, {redirect, Hosts}, State = #s{mx = MX, cx = CX}) -> NextMX = mx_del_monitor(Conn, attempt, MX), {Unassigned, NextCX} = cx_redirect(Conn, Hosts, CX), - {NewMX, NewCX} = ensure_connection(Unassigned, NextMX, NextCX), + {NewMX, NewCX} = ensure_connections(Unassigned, NextMX, NextCX), State#s{mx = NewMX, cx = NewCX}; -do_report(Conn, failed, State = #s{mx = MX, cx = CX}) -> +do_report(Conn, failed, State = #s{mx = MX}) -> NewMX = mx_del_monitor(Conn, attempt, MX), failed(Conn, State#s{mx = NewMX}); do_report(Conn, disconnected, State = #s{mx = MX}) -> @@ -806,21 +843,30 @@ do_report(Conn, disconnected, State = #s{mx = MX}) -> disconnected(Conn, State#s{mx = NewMX}). -failed(Conn, State#s{mx = MX, cx = CX}) -> +-spec failed(Conn, State) -> NewState + when Conn :: pid(), + State :: state(), + NewState :: state(). + +failed(Conn, State = #s{mx = MX, cx = CX}) -> {Realms, NextCX} = cx_failed(Conn, CX), - {NewMX, NewCX} = ensure_connection(Realms, MX, NextCX), + {NewMX, NewCX} = ensure_connections(Realms, MX, NextCX), State#s{mx = NewMX, cx = NewCX}. - + + +-spec disconnected(Conn, State) -> NewState + when Conn :: pid(), + State :: state(), + NewState :: state(). disconnected(Conn, State = #s{actions = Actions, requests = Requests, mx = MX, cx = CX}) -> - {Pending, LostSubs, ScrubbedCX} = cx_disconnected(Conn, CX), - Unassigned = cx_unassigned(ScrubbedCX), + {Pending, LostSubs, Unassigned, ScrubbedCX} = cx_disconnected(Conn, CX), ReSubs = [{S, {subscribe, P}} || {S, P} <- LostSubs], {Dequeued, NewRequests} = maps:fold(dequeue(Pending), {#{}, #{}}, Requests), ReReqs = maps:to_list(Dequeued), NewActions = ReReqs ++ ReSubs ++ Actions, - {NewMX, NewCX} = ensure_connection(Unassigned, ScrubbedMX, ScrubbedCX), + {NewMX, NewCX} = ensure_connections(Unassigned, MX, ScrubbedCX), State#s{actions = NewActions, requests = NewRequests, mx = NewMX, @@ -848,32 +894,48 @@ dequeue(Pending) -> end. --spec ensure_connection(Realms, MX, CX) -> {NewMX, NewCX} +-spec ensure_connections(Realms, MX, CX) -> {NewMX, NewCX} when Realms :: [zx:realm()], MX :: monitor_index(), CX :: conn_index(), NewMX :: monitor_index(), NewCX :: conn_index(). %% @private -%% Initiates a connection to a single realm at a time, taking into account whether -%% connected but unassigned nodes are alterantive providers of the needed realm. -%% Returns updated monitor and connection indices. +%% Check the list of unprovided realms with all available connections that can provide +%% it, and allocate accordingly. If any realms cannot be provided by existing +%% connections, new connections are initiated for all unprovided realms. -ensure_connection([Realm | Realms], MX, CX = #cx{realms = RMetas}) -> - {NewMX, NewCX} = +ensure_connections(Realms, MX, CX) -> + {NextCX, Unavailable} = reassign_conns(Realms, CX, []), + {ok, NewMX, NewCX} = init_connections(Unavailable, MX, NextCX), + {NewMX, NewCX}. + + +-spec reassign_conns(Realms, CX, Unavailable) -> {NewCX, NewUnavailable} + when Realms :: [zx:realm()], + CX :: conn_index(), + Unavailable :: [zx:realm()], + NewCX :: conn_index(), + NewUnavailable :: [zx:realm()]. +%% @private +%% Finds connections that provide a requested realm and assigns that connection to +%% take over realm provision. Returns the updated CX and a list of all the realms +%% that could not be provided by any available connection. + +reassign_conns([Realm | Realms], CX = #cx{realms = RMetas}, Unassigned) -> + {NewUnassigned, NewCX} = case maps:get(Realm, RMetas) of #rmeta{available = []} -> - {ok, NextMX, NextCX} = init_connections([Realm], MX, CX), - {NextMX, NextCX}; + {[Realm | Unassigned], CX}; Meta = #rmeta{available = [Conn | Conns]} -> NewMeta = Meta#rmeta{assigned = Conn, available = Conns}, NewRMetas = maps:put(Realm, NewMeta, RMetas), NextCX = CX#cx{realms = NewRMetas}, - {MX, NextCX} + {Unassigned, NextCX} end, - ensure_connection(Realms, NewMX, NewCX); -ensure_connection([], MX, CX) -> - {MX, CX}. + reassign_conns(Realms, NewCX, NewUnassigned); +reassign_conns([], CX, Unassigned) -> + {CX, Unassigned}. -spec do_result(ID, Result, State) -> NewState @@ -915,7 +977,7 @@ handle_orphan_result(ID, Result, Dropped) -> ok = log(info, Message, [ID, Request, Result]), NewDropped; error -> - Message = "Received unknown request result ~tp: ~tp", + Message = "Received untracked request result ~tp: ~tp", ok = log(warning, Message, [ID, Result]), Dropped end. @@ -954,6 +1016,16 @@ eval_queue(State = #s{actions = Actions}) -> eval_queue(InOrder, State#s{actions = []}). +-spec eval_queue(Actions, State) -> NewState + when Actions :: [action()], + State :: state(), + NewState :: state(). +%% @private +%% This is essentially a big, gnarly fold over the action list with State as the +%% accumulator. It repacks the State#s.actions list with whatever requests were not +%% able to be handled and updates State in whatever way necessary according to the +%% handled requests. + eval_queue([], State) -> State; eval_queue([Action = {request, Pid, ID, Message} | Rest], @@ -966,7 +1038,7 @@ eval_queue([Action = {request, Pid, ID, Message} | Rest], {Actions, NextRequests, NextMX, NextCX}; {result, Response} -> Pid ! Response, - {Actions, Requests, CX}; + {Actions, Requests, MX, CX}; wait -> NextActions = [Action | Actions], NextMX = mx_add_monitor(Pid, requestor, MX), @@ -986,7 +1058,7 @@ eval_queue([Action = {subscribe, Pid, Package} | Rest], ok = zx_conn:subscribe(Conn, Package), NextMX = mx_add_monitor(Pid, subscriber, MX), {Actions, NextMX, NextCX}; - {have_sub, NextCX} + {have_sub, NextCX} -> NextMX = mx_add_monitor(Pid, subscriber, MX), {Actions, NextMX, NextCX}; unassigned -> @@ -1002,8 +1074,8 @@ eval_queue([{unsubscribe, Pid, Package} | Rest], {ok, NewMX} = mx_del_monitor(Pid, {subscription, Package}, MX), NewCX = case cx_del_sub(Pid, Package, CX) of - {drop_sub, NextCX} -> - ok = zx_conn:unsubscribe(Conn, Package), + {{drop_sub, ConnPid}, NextCX} -> + ok = zx_conn:unsubscribe(ConnPid, Package), NextCX; {keep_sub, NextCX} -> NextCX; @@ -1026,10 +1098,13 @@ eval_queue([{unsubscribe, Pid, Package} | Rest], | wait, NewCX :: conn_index(), Response :: result(). +%% @private +%% Routes a request to the correct realm connector, if it is available. If it is not +%% available but configured it will return `wait' indicating that the caller should +%% repack the request and attempt to re-evaluate it later. If the realm is not +%% configured at all, the process is short-circuited by forming an error response +%% directly. -dispatch_request(list, ID, CX) -> - Realms = cx_realms(CX), - {result, ID, Realms}; dispatch_request(Action, ID, CX) -> Realm = element(2, Action), case cx_pre_send(Realm, ID, CX) of @@ -1062,27 +1137,20 @@ clear_monitor(Pid, mx = MX, cx = CX}) -> case mx_crashed_monitor(Pid, MX) of - {attempt, NextMX} -> - failed( - {conn, NextMX} -> - disconnected(Pid, State#s{mx = NextMX}); - {{Reqs, Subs}, NextMX} -> - case mx_lookup_category(Pid, MX) of - attempt -> - do_report(Pid, failed, State); - conn -> - do_report(Pid, disconnected, State); - {Reqs, Subs} -> + {attempt, NewMX} -> + failed(Pid, State#s{mx = NewMX}); + {conn, NewMX} -> + disconnected(Pid, State#s{mx = NewMX}); + {{Reqs, Subs}, NewMX} -> NewActions = drop_actions(Pid, Actions), {NewDropped, NewRequests} = drop_requests(Pid, Dropped, Requests), NewCX = cx_clear_client(Pid, Reqs, Subs, CX), - NewMX = mx_del_monitor(Pid, crashed, MX), State#s{actions = NewActions, requests = NewRequests, dropped = NewDropped, mx = NewMX, cx = NewCX}; - error -> + unknown -> Unexpected = {'DOWN', Ref, process, Pid, Reason}, ok = log(warning, "Unexpected info: ~tp", [Unexpected]), State @@ -1121,116 +1189,8 @@ drop_requests(ReqIDs, Dropped, Requests) -> lists:fold(Partition, {Dropped, Requests}, ReqIDs). -%-spec do_query_latest(Object, State) -> {Result, NewState} -% when Object :: zx:package() | zx:package_id(), -% State :: state(), -% Result :: {ok, zx:version()} -% | {error, Reason}, -% Reason :: bad_realm -% | bad_package -% | bad_version, -% NewState :: state(). -%% @private -%% Queries a zomp realm for the latest version of a package or package -%% version (complete or incomplete version number). -% -%do_query_latest(Socket, {Realm, Name}) -> -% ok = zx_net:send(Socket, {latest, Realm, Name}), -% receive -% {tcp, Socket, Bin} -> binary_to_term(Bin) -% after 5000 -> {error, timeout} -% end; -%do_query_latest(Socket, {Realm, Name, Version}) -> -% ok = zx_net:send(Socket, {latest, Realm, Name, Version}), -% receive -% {tcp, Socket, Bin} -> binary_to_term(Bin) -% after 5000 -> {error, timeout} -% end. - - -%-spec do_fetch(PackageIDs, State) -> NewState -% when PackageIDs :: [zx:package_id()], -% State :: state(), -% NewState :: state(), -% Result :: ok -% | {error, Reason}, -% Reason :: bad_realm -% | bad_package -% | bad_version -% | network. -%% @private -%% -% -%do_fetch(PackageIDs, State) -> -% FIXME: Need to create a job queue divided by realm and dispatched to connectors, -% and cleared from the master pending queue kept here by the daemon as the -% workers succeed. Basic task queue management stuff... which never existed -% in ZX before... grrr... -% case scrub(PackageIDs) of -% [] -> -% ok; -% Needed -> -% Partitioned = partition_by_realm(Needed), -% EnsureDeps = -% fun({Realm, Packages}) -> -% ok = zx_conn:queue_package(Pid, Realm, Packages), -% log(info, "Disconnecting from realm: ~ts", [Realm]) -% end, -% lists:foreach(EnsureDeps, Partitioned) -% end. -% -% -%partition_by_realm(PackageIDs) -> -% PartitionMap = lists:foldl(fun partition_by_realm/2, #{}, PackageIDs), -% maps:to_list(PartitionMap). -% -% -%partition_by_realm({R, P, V}, M) -> -% maps:update_with(R, fun(Ps) -> [{P, V} | Ps] end, [{P, V}], M). -% -% -%ensure_deps(_, _, []) -> -% ok; -%ensure_deps(Socket, Realm, [{Name, Version} | Rest]) -> -% ok = ensure_dep(Socket, {Realm, Name, Version}), -% ensure_deps(Socket, Realm, Rest). -% -% -%-spec ensure_dep(gen_tcp:socket(), package_id()) -> ok | no_return(). -%% @private -%% Given an PackageID as an argument, check whether its package file exists in the -%% system cache, and if not download it. Should return `ok' whenever the file is -%% sourced, but exit with an error if it cannot locate or acquire the package. -% -%ensure_dep(Socket, PackageID) -> -% ZrpFile = filename:join("zrp", namify_zrp(PackageID)), -% ok = -% case filelib:is_regular(ZrpFile) of -% true -> ok; -% false -> fetch(Socket, PackageID) -% end, -% ok = install(PackageID), -% build(PackageID). -% -% -%-spec scrub(Deps) -> Scrubbed -% when Deps :: [package_id()], -% Scrubbed :: [package_id()]. -%% @private -%% Take a list of dependencies and return a list of dependencies that are not yet -%% installed on the system. -% -%scrub([]) -> -% []; -%scrub(Deps) -> -% lists:filter(fun(PackageID) -> not zx_lib:installed(PackageID) end, Deps). - - %%% Monitor Index ADT Interface Functions -%%% -%%% Very simple structure, but explicit handling of it becomes bothersome in other -%%% code, so it is all just packed down here. -spec mx_new() -> monitor_index(). %% @private @@ -1288,7 +1248,7 @@ mx_upgrade_conn(Pid, MX) -> when Conn :: pid(), Category :: attempt | conn - | {requestor, id()}, + | {requestor, id()} | {subscriber, Sub :: tuple()}, MX :: monitor_index(), NewMX :: monitor_index(). @@ -1311,12 +1271,12 @@ mx_del_monitor(Pid, {requestor, ID}, MX) -> true = demonitor(Ref, [flush]), NextMX; {{Ref, {Reqs, Subs}}, NextMX} when Reqs > 0 -> - NewReqs = lists:subtract(ID, Reqs), - maps:put(Pid, {NewReqs, Subs}, NextMX) - end, + NewReqs = lists:delete(ID, Reqs), + maps:put(Pid, {Ref, {NewReqs, Subs}}, NextMX) + end; mx_del_monitor(Pid, {subscriber, Sub}, MX) -> case maps:take(Pid, MX) of - {{Ref, {[], [Package]}}, NextMX} -> + {{Ref, {[], [Sub]}}, NextMX} -> true = demonitor(Ref, [flush]), NextMX; {{Ref, {Reqs, Subs}}, NextMX} when Subs > 0 -> @@ -1338,27 +1298,10 @@ mx_del_monitor(Pid, {subscriber, Sub}, MX) -> mx_crashed_monitor(Pid, MX) -> case maps:take(Pid, MX) of {{Ref, Type}, NewMX} -> - true = demonitor(Mon, [flush]), + true = demonitor(Ref, [flush]), {Type, NewMX}; error -> - error - end. - - --spec mx_lookup_category(Pid, MX) -> Result - when Pid :: pid(), - MX :: monitor_index(), - Result :: attempt - | conn - | {Reqs :: [reference()], Subs :: [zx:package()]} - | error. -%% @private -%% Lookup a monitor's categories. - -mx_lookup_category(Pid, MX) -> - case maps:find(Pid, MX) of - {ok, Mon} -> element(2, Mon); - error -> error + unknown end. @@ -1672,7 +1615,7 @@ cx_connected(Available, Pid, CX = #cx{attempts = Attempts}) -> -spec cx_connected(A, Available, Conn, CX) -> {NewA, NewCX} when A :: unassigned | assigned, Available :: [{zx:realm(), zx:serial()}], - Conn :: {pid(), zx:host(), [zx:realm()]}, + Conn :: connection(), CX :: conn_index(), NewA :: unassigned | assigned, NewCX :: conn_index(). @@ -1704,7 +1647,7 @@ cx_connected(A, -spec cx_connected(A, Realm, Conn, Meta, CX) -> {NewA, NewCX} when A :: unassigned | assigned, Realm :: zx:host(), - Conn :: {pid(), zx:host(), [zx:realm()]}, + Conn :: connection(), Meta :: realm_meta(), CX :: conn_index(), NewA :: unassigned | assigned, @@ -1785,6 +1728,12 @@ cx_failed(Conn, CX = #cx{attempts = Attempts}) -> CX :: conn_index(), Unassigned :: [zx:realm()], NewCX :: conn_index(). +%% @private +%% Remove a redirected connection attempt from CX, add its redirect hosts to the +%% mirror queue, and proceed to make further connection atempts to all unassigned +%% realms. This can cause an inflationary number of new connection attempts, but this +%% is considered preferrable because the more mirrors fail the longer the user is +%% waiting and the more urgent the need to discover a working node becomes. cx_redirect(Conn, Hosts, CX = #cx{attempts = Attempts}) -> NewAttempts = lists:keydelete(Conn, 1, Attempts), @@ -1810,7 +1759,7 @@ cx_redirect([{Host, Provided} | Rest], CX = #cx{realms = Realms}) -> NewMeta = Meta#rmeta{mirrors = NewMirrors}, maps:put(R, NewMeta, Rs); error -> - R + Rs end end, NewRealms = lists:foldl(Apply, Realms, Provided), @@ -1823,7 +1772,7 @@ cx_redirect([], CX) -> when CX :: conn_index(), Unassigned :: [zx:realm()]. %% @private -%% Scan the CX record for unassigned realms;return a list of all unassigned +%% Scan CX#cx.realms for unassigned realms and return a list of all unassigned %% realm names. cx_unassigned(#cx{realms = Realms}) -> @@ -1837,12 +1786,13 @@ cx_unassigned(#cx{realms = Realms}) -> maps:fold(NotAssigned, [], Realms). --spec cx_disconnected(Conn, CX) -> {Requests, Subs, NewCX} - when Conn :: pid(), - CX :: conn_index(), - Requests :: [reference()], - Subs :: [zx:package()], - NewCX :: conn_index(). +-spec cx_disconnected(Conn, CX) -> {Requests, Subs, Unassigned, NewCX} + when Conn :: pid(), + CX :: conn_index(), + Requests :: [id()], + Subs :: [zx:package()], + Unassigned :: [zx:realm()], + NewCX :: conn_index(). %% @private %% An abstract data handler which is called whenever a connection terminates. %% This function removes all data related to the disconnected pid and its assigned @@ -1854,7 +1804,8 @@ cx_disconnected(Pid, CX = #cx{realms = Realms, conns = Conns}) -> #conn{host = Host, requests = Requests, subs = Subs} = Conn, NewRealms = cx_scrub_assigned(Pid, Host, Realms), NewCX = CX#cx{realms = NewRealms, conns = NewConns}, - {Requests, Subs, NewCX}. + Unassigned = cx_unassigned(NewCX), + {Requests, Subs, Unassigned, NewCX}. -spec cx_scrub_assigned(Pid, Host, Realms) -> NewRealms @@ -1960,11 +1911,11 @@ cx_add_sub(Subscriber, Channel, CX = #cx{conns = Conns}) -> NewCX :: conn_index(). cx_maybe_new_sub(Conn = #conn{pid = ConnPid, subs = Subs}, - Sub = {Subscriber, Channel}, + Sub = {_, Channel}, CX = #cx{conns = Conns}) -> - NewSubs = [{Subscriber, Channel} | Subs], + NewSubs = [Sub | Subs], NewConn = Conn#conn{subs = NewSubs}, - NewConns = [NewConn | NextConns], + NewConns = [NewConn | Conns], NewCX = CX#cx{conns = NewConns}, case lists:keymember(Channel, 2, Subs) of false -> {need_sub, ConnPid, NewCX}; @@ -1990,25 +1941,35 @@ cx_del_sub(Subscriber, Channel, CX = #cx{conns = Conns}) -> case cx_resolve(Realm, CX) of {ok, Pid} -> {value, Conn, NewConns} = lists:keytake(Pid, #conn.pid, Conns), - cx_maybe_last_sub(Conn, {Subscriber, Channel}, CX#cx{conns = NewConns}; + cx_maybe_last_sub(Conn, {Subscriber, Channel}, CX#cx{conns = NewConns}); Other -> Other end. +-spec cx_maybe_last_sub(Conn, Sub, CX) -> {Verdict, NewCX} + when Conn :: connection(), + Sub :: {pid(), term()}, + CX :: conn_index(), + Verdict :: {drop_sub, Conn :: pid()} | keep_sub, + NewCX :: conn_index(). +%% @private +%% Tells us whether a sub is still valid for any clients. If a sub is unsubbed by all +%% then it needs to be unsubscribed at the upstream node. + cx_maybe_last_sub(Conn = #conn{pid = ConnPid, subs = Subs}, - Sub = {Subscriber, Channel}, + Sub = {_, Channel}, CX = #cx{conns = Conns}) -> NewSubs = lists:delete(Sub, Subs), NewConn = Conn#conn{subs = NewSubs}, - NewConns = [NewConn | NextConns], + NewConns = [NewConn | Conns], NewCX = CX#cx{conns = NewConns}, - MaybeDrop = + Verdict = case lists:keymember(Channel, 2, NewSubs) of - false -> drop_sub; + false -> {drop_sub, ConnPid}; true -> keep_sub end, - {MaybeDrop, NewCX}. + {Verdict, NewCX}. -spec cx_get_subscribers(Conn, Channel, CX) -> Subscribers @@ -2022,6 +1983,16 @@ cx_get_subscribers(Conn, Channel, #cx{conns = Conns}) -> lists:fold(registered_to(Channel), [], Subs). +-spec registered_to(Channel) -> fun(({P, C}, A) -> NewA) + when Channel :: term(), + P :: pid(), + C :: term(), + A :: [pid()], + NewA :: [pid()]. +%% @private +%% Matching function that closes over a given channel in a subscriber list. +%% This function exists mostly to make its parent function read nicely. + registered_to(Channel) -> fun({P, C}, A) -> case C == Channel of @@ -2031,8 +2002,15 @@ registered_to(Channel) -> end. +-spec cx_clear_client(Pid, DeadReqs, DeadSubs, CX) -> NewCX + when Pid :: pid(), + DeadReqs :: [id()], + DeadSubs :: [term()], + CX :: conn_index(), + NewCX :: conn_index(). + cx_clear_client(Pid, DeadReqs, DeadSubs, CX = #cx{conns = Conns}) -> - DropSubs = [{S, Pid} || S <- DeadSubs], + DropSubs = [{Pid, Sub} || Sub <- DeadSubs], Clear = fun(C = #conn{requests = Requests, subs = Subs}) -> NewSubs = lists:subtract(Subs, DropSubs),