├── .gitignore ├── CONTRIBUTORS.md ├── LICENSE ├── Makefile ├── README.md ├── apps └── riak_pg │ ├── ebin │ └── src ├── include └── riak_pg.hrl ├── rebar ├── rebar.config ├── rebar.config.lock ├── rel ├── files │ ├── app.config │ ├── erl │ ├── nodetool │ ├── riak_pg │ ├── riak_pg-admin │ └── vm.args ├── gen_dev ├── reltool.config ├── vars.config └── vars │ └── dev_vars.config.src ├── src ├── riak_pg.app.src ├── riak_pg.erl ├── riak_pg_app.erl ├── riak_pg_console.erl ├── riak_pg_delete_fsm.erl ├── riak_pg_delete_fsm_sup.erl ├── riak_pg_groups_fsm.erl ├── riak_pg_groups_fsm_sup.erl ├── riak_pg_join_fsm.erl ├── riak_pg_join_fsm_sup.erl ├── riak_pg_leave_fsm.erl ├── riak_pg_leave_fsm_sup.erl ├── riak_pg_members_fsm.erl ├── riak_pg_members_fsm_sup.erl ├── riak_pg_node_event_handler.erl ├── riak_pg_ring_event_handler.erl ├── riak_pg_sup.erl ├── riak_pg_util.erl └── riak_pg_vnode.erl └── tools.mk /.gitignore: -------------------------------------------------------------------------------- 1 | *.beam 2 | .eunit 3 | deps/* 4 | ebin/* 5 | dev 6 | apps/riak_pubsub/src/* 7 | tags 8 | rel/vars/*_vars.config 9 | rel/riak_pg 10 | .DS_Store 11 | doc 12 | .qc/ 13 | .local_dialyzer_plt 14 | *.dump 15 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | Mark Steele : 2 | - Removed unused vnode code 3 | - Added coverage command to list groups. 4 | - Switched from dicts to CRDT maps 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: deps 2 | 3 | all: deps compile 4 | 5 | compile: deps 6 | ./rebar compile 7 | 8 | deps: 9 | ./rebar get-deps 10 | 11 | locked-deps: 12 | ./rebar -C rebar.config.lock get-deps 13 | 14 | clean: 15 | ./rebar clean 16 | 17 | distclean: clean devclean relclean 18 | ./rebar delete-deps 19 | 20 | rel: all 21 | ./rebar generate 22 | 23 | relclean: 24 | rm -rf rel/riak_pg 25 | 26 | stage : rel 27 | $(foreach dep,$(wildcard deps/*), rm -rf rel/riak_pg/lib/$(shell basename $(dep))-* && ln -sf $(abspath $(dep)) rel/riak_pg/lib;) 28 | $(foreach app,$(wildcard apps/*), rm -rf rel/riak_pg/lib/$(shell basename $(app))-* && ln -sf $(abspath $(app)) rel/riak_pg/lib;) 29 | 30 | DIALYZER_APPS = kernel stdlib sasl erts ssl tools os_mon runtime_tools crypto inets \ 31 | xmerl webtool eunit syntax_tools compiler mnesia public_key snmp 32 | 33 | include tools.mk 34 | 35 | typer: 36 | typer --annotate -I ../ --plt $(PLT) -r src 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Riak PG 2 | 3 | ## Overview 4 | 5 | Distributed process groups with riak\_core. 6 | 7 | ## Usage 8 | 9 | Join a process to a group (no need to pre-declare) 10 | 11 | ```erlang 12 | join(term(), pid()) -> ok | {error, timeout}. 13 | ``` 14 | 15 | What about removing? 16 | 17 | ```erlang 18 | leave(term(), pid()) -> ok | {error, timeout}. 19 | ``` 20 | 21 | You can delete process groups. 22 | 23 | ```erlang 24 | delete(term()) -> ok | {error, timeout}. 25 | ``` 26 | 27 | You can also return the members, local members or connected members of a 28 | group. 29 | 30 | ```erlang 31 | members(term()) -> {ok, list(pid()) | {error, timeout}. 32 | local_members(term()) -> {ok, list(pid()) | {error, timeout}. 33 | connected_members(term()) -> {ok, list(pid()) | {error, timeout}. 34 | ``` 35 | 36 | You can list the groups that have been created and currently have members. 37 | 38 | ```erlang 39 | groups() -> {ok, list(term())} | {error, timeout} 40 | ``` 41 | 42 | ## Copyright 43 | 44 | Copyright (C) 2013 Christopher Meiklejohn 45 | -------------------------------------------------------------------------------- /apps/riak_pg/ebin: -------------------------------------------------------------------------------- 1 | ../../ebin -------------------------------------------------------------------------------- /apps/riak_pg/src: -------------------------------------------------------------------------------- 1 | ../../src -------------------------------------------------------------------------------- /include/riak_pg.hrl: -------------------------------------------------------------------------------- 1 | %% @author Christopher Meiklejohn 2 | %% @copyright 2013 Christopher Meiklejohn. 3 | %% @doc Library definitions. 4 | 5 | -define(PRINT(Var), 6 | io:format("DEBUG: ~p:~p - ~p~n~n ~p~n~n", [?MODULE, ?LINE, ??Var, Var])). 7 | 8 | -define(N, 3). 9 | -define(R, 2). 10 | -define(W, 2). 11 | -------------------------------------------------------------------------------- /rebar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmeiklejohn/riak_pg/32d46bc1909144cea93b8b98a652a00f6520ffdb/rebar -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {sub_dirs, ["rel"]}. 2 | 3 | {cover_enabled, true}. 4 | 5 | {edoc_opts, [{preprocess, true}]}. 6 | 7 | {erl_opts, [warnings_as_errors, 8 | {parse_transform, lager_transform}]}. 9 | 10 | {eunit_opts, [verbose]}. 11 | 12 | {deps, [ 13 | {riak_dt, ".*", 14 | {git, "git://github.com/basho/riak_dt", "develop"}}, 15 | {riak_core, ".*", 16 | {git, "git://github.com/basho/riak_core", "master"}}, 17 | {rebar_lock_deps_plugin, ".*", 18 | {git, "https://github.com/lukyanov/rebar-lock-deps.git", {branch, "master"}}} 19 | ]}. 20 | 21 | 22 | {plugins, [rebar_lock_deps_plugin]}. 23 | -------------------------------------------------------------------------------- /rebar.config.lock: -------------------------------------------------------------------------------- 1 | %% THIS FILE IS GENERATED. EDIT WITH CAUTION %%~n 2 | 3 | {sub_dirs,["rel"]}. 4 | {cover_enabled,true}. 5 | {edoc_opts,[{preprocess,true}]}. 6 | {erl_opts,[warnings_as_errors,{parse_transform,lager_transform}]}. 7 | {eunit_opts,[verbose]}. 8 | {deps,[{basho_stats,".*", 9 | {git,"git://github.com/basho/basho_stats.git", 10 | "19c532af235ae675439d491b329c55c2f9b02deb"}}, 11 | {bear,".*", 12 | {git,"git://github.com/basho/bear.git", 13 | "da820a13c607c3f816ee8b83c587266da5389761"}}, 14 | {cuttlefish,".*", 15 | {git,"git://github.com/basho/cuttlefish.git", 16 | "b2ce0b15687f97259031f9f42e25ae52cdee3bab"}}, 17 | {eleveldb,".*", 18 | {git,"git://github.com/basho/eleveldb.git", 19 | "2c9c5a5f66255266145154bb3ca975087f2a308f"}}, 20 | {folsom,".*", 21 | {git,"git://github.com/basho/folsom.git", 22 | "e80bd013c8ea906c4f14cba38b92e95116415e23"}}, 23 | {getopt,".*", 24 | {git,"git://github.com/jcomellas/getopt.git", 25 | "659a28f4145bc9843598972854299dc4ea77e4cb"}}, 26 | {goldrush,".*", 27 | {git,"git://github.com/DeadZen/goldrush.git", 28 | "71e63212f12c25827e0c1b4198d37d5d018a7fec"}}, 29 | {lager,".*", 30 | {git,"git://github.com/basho/lager.git", 31 | "b6b6cebcb27ccff8acc59ae775acebc2f52e4926"}}, 32 | {meck,".*", 33 | {git,"git://github.com/basho/meck.git", 34 | "2b25a30a8688f94106d07f23a9f1fb523ac00f08"}}, 35 | {neotoma,".*", 36 | {git,"git://github.com/seancribbs/neotoma.git", 37 | "760928ec8870da02eb11bccb501e2700925d06c6"}}, 38 | {pbkdf2,".*", 39 | {git,"git://github.com/basho/erlang-pbkdf2.git", 40 | "7076584f5377e98600a7e2cb81980b2992fb2f71"}}, 41 | {poolboy,".*", 42 | {git,"git://github.com/basho/poolboy.git", 43 | "84d836ab49da618d1e3fb3947207bf9b7fcea335"}}, 44 | {rebar_lock_deps_plugin,".*", 45 | {git,"https://github.com/lukyanov/rebar-lock-deps.git", 46 | "58366997aaa3b9f0876b10c4421b64c2f73150cd"}}, 47 | {riak_core,".*", 48 | {git,"git://github.com/basho/riak_core", 49 | "8a0d80547fe8ee2fb95f7f657050e4232fe92612"}}, 50 | {riak_dt,".*", 51 | {git,"git://github.com/basho/riak_dt", 52 | "a2986bccd1cc42facdfe739495c6d13762ae0f37"}}, 53 | {riak_ensemble,".*", 54 | {git,"git://github.com/basho/riak_ensemble", 55 | "e0652c64cda9355dc429f4a4c3747c5fdf017722"}}, 56 | {riak_sysmon,".*", 57 | {git,"git://github.com/basho/riak_sysmon.git", 58 | "8362bc1046c8f5c749b1f6e61eb9571b0d5a5f47"}}]}. 59 | {plugins,[rebar_lock_deps_plugin]}. 60 | 61 | -------------------------------------------------------------------------------- /rel/files/app.config: -------------------------------------------------------------------------------- 1 | [ 2 | %% Riak Core config 3 | {riak_core, [ 4 | %% Default location of ringstate 5 | {ring_state_dir, "{{ring_state_dir}}"}, 6 | 7 | %% http is a list of IP addresses and TCP ports that the Riak 8 | %% HTTP interface will bind. 9 | {http, [ {"{{web_ip}}", {{web_port}} } ]}, 10 | 11 | %% riak_handoff_port is the TCP port that Riak uses for 12 | %% intra-cluster data handoff. 13 | {handoff_port, {{handoff_port}} } 14 | ]}, 15 | 16 | %% SASL config 17 | {sasl, [ 18 | {sasl_error_logger, {file, "log/sasl-error.log"}}, 19 | {errlog_type, error}, 20 | {error_logger_mf_dir, "log/sasl"}, % Log directory 21 | {error_logger_mf_maxbytes, 10485760}, % 10 MB max file size 22 | {error_logger_mf_maxfiles, 5} % 5 files max 23 | ]} 24 | ]. 25 | -------------------------------------------------------------------------------- /rel/files/erl: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## This script replaces the default "erl" in erts-VSN/bin. This is necessary 4 | ## as escript depends on erl and in turn, erl depends on having access to a 5 | ## bootscript (start.boot). Note that this script is ONLY invoked as a side-effect 6 | ## of running escript -- the embedded node bypasses erl and uses erlexec directly 7 | ## (as it should). 8 | ## 9 | ## Note that this script makes the assumption that there is a start_clean.boot 10 | ## file available in $ROOTDIR/release/VSN. 11 | 12 | # Determine the abspath of where this script is executing from. 13 | ERTS_BIN_DIR=$(cd ${0%/*} && pwd) 14 | 15 | # Now determine the root directory -- this script runs from erts-VSN/bin, 16 | # so we simply need to strip off two dirs from the end of the ERTS_BIN_DIR 17 | # path. 18 | ROOTDIR=${ERTS_BIN_DIR%/*/*} 19 | 20 | # Parse out release and erts info 21 | START_ERL=`cat $ROOTDIR/releases/start_erl.data` 22 | ERTS_VSN=${START_ERL% *} 23 | APP_VSN=${START_ERL#* } 24 | 25 | BINDIR=$ROOTDIR/erts-$ERTS_VSN/bin 26 | EMU=beam 27 | PROGNAME=`echo $0 | sed 's/.*\\///'` 28 | CMD="$BINDIR/erlexec" 29 | export EMU 30 | export ROOTDIR 31 | export BINDIR 32 | export PROGNAME 33 | 34 | exec $CMD -boot $ROOTDIR/releases/$APP_VSN/start_clean ${1+"$@"} 35 | -------------------------------------------------------------------------------- /rel/files/nodetool: -------------------------------------------------------------------------------- 1 | %% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- 2 | %% ex: ft=erlang ts=4 sw=4 et 3 | %% ------------------------------------------------------------------- 4 | %% 5 | %% nodetool: Helper Script for interacting with live nodes 6 | %% 7 | %% ------------------------------------------------------------------- 8 | 9 | main(Args) -> 10 | ok = start_epmd(), 11 | %% Extract the args 12 | {RestArgs, TargetNode} = process_args(Args, [], undefined), 13 | 14 | %% See if the node is currently running -- if it's not, we'll bail 15 | case {net_kernel:hidden_connect_node(TargetNode), net_adm:ping(TargetNode)} of 16 | {true, pong} -> 17 | ok; 18 | {_, pang} -> 19 | io:format("Node ~p not responding to pings.\n", [TargetNode]), 20 | halt(1) 21 | end, 22 | 23 | case RestArgs of 24 | ["ping"] -> 25 | %% If we got this far, the node already responsed to a ping, so just dump 26 | %% a "pong" 27 | io:format("pong\n"); 28 | ["stop"] -> 29 | io:format("~p\n", [rpc:call(TargetNode, init, stop, [], 60000)]); 30 | ["restart"] -> 31 | io:format("~p\n", [rpc:call(TargetNode, init, restart, [], 60000)]); 32 | ["reboot"] -> 33 | io:format("~p\n", [rpc:call(TargetNode, init, reboot, [], 60000)]); 34 | ["rpc", Module, Function | RpcArgs] -> 35 | case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function), 36 | [RpcArgs], 60000) of 37 | ok -> 38 | ok; 39 | {badrpc, Reason} -> 40 | io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]), 41 | halt(1); 42 | _ -> 43 | halt(1) 44 | end; 45 | ["rpcterms", Module, Function, ArgsAsString] -> 46 | case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function), 47 | consult(ArgsAsString), 60000) of 48 | {badrpc, Reason} -> 49 | io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]), 50 | halt(1); 51 | Other -> 52 | io:format("~p\n", [Other]) 53 | end; 54 | Other -> 55 | io:format("Other: ~p\n", [Other]), 56 | io:format("Usage: nodetool {ping|stop|restart|reboot}\n") 57 | end, 58 | net_kernel:stop(). 59 | 60 | process_args([], Acc, TargetNode) -> 61 | {lists:reverse(Acc), TargetNode}; 62 | process_args(["-setcookie", Cookie | Rest], Acc, TargetNode) -> 63 | erlang:set_cookie(node(), list_to_atom(Cookie)), 64 | process_args(Rest, Acc, TargetNode); 65 | process_args(["-name", TargetName | Rest], Acc, _) -> 66 | ThisNode = append_node_suffix(TargetName, "_maint_"), 67 | {ok, _} = net_kernel:start([ThisNode, longnames]), 68 | process_args(Rest, Acc, nodename(TargetName)); 69 | process_args(["-sname", TargetName | Rest], Acc, _) -> 70 | ThisNode = append_node_suffix(TargetName, "_maint_"), 71 | {ok, _} = net_kernel:start([ThisNode, shortnames]), 72 | process_args(Rest, Acc, nodename(TargetName)); 73 | process_args([Arg | Rest], Acc, Opts) -> 74 | process_args(Rest, [Arg | Acc], Opts). 75 | 76 | 77 | start_epmd() -> 78 | [] = os:cmd(epmd_path() ++ " -daemon"), 79 | ok. 80 | 81 | epmd_path() -> 82 | ErtsBinDir = filename:dirname(escript:script_name()), 83 | Name = "epmd", 84 | case os:find_executable(Name, ErtsBinDir) of 85 | false -> 86 | case os:find_executable(Name) of 87 | false -> 88 | io:format("Could not find epmd.~n"), 89 | halt(1); 90 | GlobalEpmd -> 91 | GlobalEpmd 92 | end; 93 | Epmd -> 94 | Epmd 95 | end. 96 | 97 | 98 | nodename(Name) -> 99 | case string:tokens(Name, "@") of 100 | [_Node, _Host] -> 101 | list_to_atom(Name); 102 | [Node] -> 103 | [_, Host] = string:tokens(atom_to_list(node()), "@"), 104 | list_to_atom(lists:concat([Node, "@", Host])) 105 | end. 106 | 107 | append_node_suffix(Name, Suffix) -> 108 | case string:tokens(Name, "@") of 109 | [Node, Host] -> 110 | list_to_atom(lists:concat([Node, Suffix, os:getpid(), "@", Host])); 111 | [Node] -> 112 | list_to_atom(lists:concat([Node, Suffix, os:getpid()])) 113 | end. 114 | 115 | 116 | %% 117 | %% Given a string or binary, parse it into a list of terms, ala file:consult/0 118 | %% 119 | consult(Str) when is_list(Str) -> 120 | consult([], Str, []); 121 | consult(Bin) when is_binary(Bin)-> 122 | consult([], binary_to_list(Bin), []). 123 | 124 | consult(Cont, Str, Acc) -> 125 | case erl_scan:tokens(Cont, Str, 0) of 126 | {done, Result, Remaining} -> 127 | case Result of 128 | {ok, Tokens, _} -> 129 | {ok, Term} = erl_parse:parse_term(Tokens), 130 | consult([], Remaining, [Term | Acc]); 131 | {eof, _Other} -> 132 | lists:reverse(Acc); 133 | {error, Info, _} -> 134 | {error, Info} 135 | end; 136 | {more, Cont1} -> 137 | consult(Cont1, eof, Acc) 138 | end. 139 | -------------------------------------------------------------------------------- /rel/files/riak_pg: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # -*- tab-width:4;indent-tabs-mode:nil -*- 3 | # ex: ts=4 sw=4 et 4 | 5 | RUNNER_SCRIPT_DIR=$(cd ${0%/*} && pwd) 6 | 7 | RUNNER_BASE_DIR=${RUNNER_SCRIPT_DIR%/*} 8 | RUNNER_ETC_DIR=$RUNNER_BASE_DIR/etc 9 | RUNNER_LOG_DIR=$RUNNER_BASE_DIR/log 10 | # Note the trailing slash on $PIPE_DIR/ 11 | PIPE_DIR=/tmp/$RUNNER_BASE_DIR/ 12 | RUNNER_USER= 13 | 14 | # Make sure this script is running as the appropriate user 15 | if [ ! -z "$RUNNER_USER" ] && [ `whoami` != "$RUNNER_USER" ]; then 16 | exec sudo -u $RUNNER_USER -i $0 $@ 17 | fi 18 | 19 | # Make sure CWD is set to runner base dir 20 | cd $RUNNER_BASE_DIR 21 | 22 | # Make sure log directory exists 23 | mkdir -p $RUNNER_LOG_DIR 24 | 25 | # Extract the target node name from node.args 26 | NAME_ARG=`grep -e '-[s]*name' $RUNNER_ETC_DIR/vm.args` 27 | if [ -z "$NAME_ARG" ]; then 28 | echo "vm.args needs to have either -name or -sname parameter." 29 | exit 1 30 | fi 31 | 32 | # Extract the target cookie 33 | COOKIE_ARG=`grep -e '-setcookie' $RUNNER_ETC_DIR/vm.args` 34 | if [ -z "$COOKIE_ARG" ]; then 35 | echo "vm.args needs to have a -setcookie parameter." 36 | exit 1 37 | fi 38 | 39 | # Identify the script name 40 | SCRIPT=`basename $0` 41 | 42 | # Parse out release and erts info 43 | START_ERL=`cat $RUNNER_BASE_DIR/releases/start_erl.data` 44 | ERTS_VSN=${START_ERL% *} 45 | APP_VSN=${START_ERL#* } 46 | 47 | # Add ERTS bin dir to our path 48 | ERTS_PATH=$RUNNER_BASE_DIR/erts-$ERTS_VSN/bin 49 | 50 | # Setup command to control the node 51 | NODETOOL="$ERTS_PATH/escript $ERTS_PATH/nodetool $NAME_ARG $COOKIE_ARG" 52 | 53 | # Check the first argument for instructions 54 | case "$1" in 55 | start) 56 | # Make sure there is not already a node running 57 | RES=`$NODETOOL ping` 58 | if [ "$RES" = "pong" ]; then 59 | echo "Node is already running!" 60 | exit 1 61 | fi 62 | HEART_COMMAND="$RUNNER_BASE_DIR/bin/$SCRIPT start" 63 | export HEART_COMMAND 64 | mkdir -p $PIPE_DIR 65 | shift # remove $1 66 | $ERTS_PATH/run_erl -daemon $PIPE_DIR $RUNNER_LOG_DIR "exec $RUNNER_BASE_DIR/bin/$SCRIPT console $@" 2>&1 67 | ;; 68 | 69 | stop) 70 | # Wait for the node to completely stop... 71 | case `uname -s` in 72 | Linux|Darwin|FreeBSD|DragonFly|NetBSD|OpenBSD) 73 | # PID COMMAND 74 | PID=`ps ax -o pid= -o command=|\ 75 | grep "$RUNNER_BASE_DIR/.*/[b]eam"|awk '{print $1}'` 76 | ;; 77 | SunOS) 78 | # PID COMMAND 79 | PID=`ps -ef -o pid= -o args=|\ 80 | grep "$RUNNER_BASE_DIR/.*/[b]eam"|awk '{print $1}'` 81 | ;; 82 | CYGWIN*) 83 | # UID PID PPID TTY STIME COMMAND 84 | PID=`ps -efW|grep "$RUNNER_BASE_DIR/.*/[b]eam"|awk '{print $2}'` 85 | ;; 86 | esac 87 | $NODETOOL stop 88 | while `kill -0 $PID 2>/dev/null`; 89 | do 90 | sleep 1 91 | done 92 | ;; 93 | 94 | restart) 95 | ## Restart the VM without exiting the process 96 | $NODETOOL restart 97 | ;; 98 | 99 | reboot) 100 | ## Restart the VM completely (uses heart to restart it) 101 | $NODETOOL reboot 102 | ;; 103 | 104 | ping) 105 | ## See if the VM is alive 106 | $NODETOOL ping 107 | ;; 108 | 109 | attach) 110 | # Make sure a node IS running 111 | RES=`$NODETOOL ping` 112 | if [ "$RES" != "pong" ]; then 113 | echo "Node is not running!" 114 | exit 1 115 | fi 116 | 117 | shift 118 | $ERTS_PATH/to_erl $PIPE_DIR 119 | ;; 120 | 121 | console|console_clean) 122 | # .boot file typically just $SCRIPT (ie, the app name) 123 | # however, for debugging, sometimes start_clean.boot is useful: 124 | case "$1" in 125 | console) BOOTFILE=$SCRIPT ;; 126 | console_clean) BOOTFILE=start_clean ;; 127 | esac 128 | # Setup beam-required vars 129 | ROOTDIR=$RUNNER_BASE_DIR 130 | BINDIR=$ROOTDIR/erts-$ERTS_VSN/bin 131 | EMU=beam 132 | PROGNAME=`echo $0 | sed 's/.*\\///'` 133 | CMD="$BINDIR/erlexec -boot $RUNNER_BASE_DIR/releases/$APP_VSN/$BOOTFILE -embedded -config $RUNNER_ETC_DIR/app.config -args_file $RUNNER_ETC_DIR/vm.args -- ${1+"$@"}" 134 | export EMU 135 | export ROOTDIR 136 | export BINDIR 137 | export PROGNAME 138 | 139 | # Dump environment info for logging purposes 140 | echo "Exec: $CMD" 141 | echo "Root: $ROOTDIR" 142 | 143 | # Log the startup 144 | logger -t "$SCRIPT[$$]" "Starting up" 145 | 146 | # Start the VM 147 | exec $CMD 148 | ;; 149 | 150 | *) 151 | echo "Usage: $SCRIPT {start|stop|restart|reboot|ping|console|console_clean|attach}" 152 | exit 1 153 | ;; 154 | esac 155 | 156 | exit 0 157 | -------------------------------------------------------------------------------- /rel/files/riak_pg-admin: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | RUNNER_SCRIPT_DIR=$(cd ${0%/*} && pwd) 4 | RUNNER_SCRIPT=${0##*/} 5 | 6 | RUNNER_BASE_DIR=${RUNNER_SCRIPT_DIR%/*} 7 | RUNNER_ETC_DIR=$RUNNER_BASE_DIR/etc 8 | RUNNER_LOG_DIR=$RUNNER_BASE_DIR/log 9 | RUNNER_USER= 10 | 11 | # Make sure this script is running as the appropriate user 12 | if [ "$RUNNER_USER" -a "x$LOGNAME" != "x$RUNNER_USER" ]; then 13 | type -p sudo > /dev/null 2>&1 14 | if [ $? -ne 0 ]; then 15 | echo "sudo doesn't appear to be installed and your EUID isn't $RUNNER_USER" 1>&2 16 | exit 1 17 | fi 18 | echo "Attempting to restart script through sudo -u $RUNNER_USER" 19 | exec sudo -u $RUNNER_USER -i $RUNNER_SCRIPT_DIR/$RUNNER_SCRIPT "$@" 20 | fi 21 | 22 | # Make sure CWD is set to runner base dir 23 | cd $RUNNER_BASE_DIR 24 | 25 | # Extract the target node name from node.args 26 | NAME_ARG=`grep -e '-[s]*name' $RUNNER_ETC_DIR/vm.args` 27 | if [ -z "$NAME_ARG" ]; then 28 | echo "vm.args needs to have either -name or -sname parameter." 29 | exit 1 30 | fi 31 | 32 | # Learn how to specify node name for connection from remote nodes 33 | echo "$NAME_ARG" | grep '^-sname' > /dev/null 2>&1 34 | if [ "X$?" = "X0" ]; then 35 | NAME_PARAM="-sname" 36 | NAME_HOST="" 37 | else 38 | NAME_PARAM="-name" 39 | echo "$NAME_ARG" | grep '@.*' > /dev/null 2>&1 40 | if [ "X$?" = "X0" ]; then 41 | NAME_HOST=`echo "${NAME_ARG}" | sed -e 's/.*\(@.*\)$/\1/'` 42 | else 43 | NAME_HOST="" 44 | fi 45 | fi 46 | 47 | # Extract the target cookie 48 | COOKIE_ARG=`grep '\-setcookie' $RUNNER_ETC_DIR/vm.args` 49 | if [ -z "$COOKIE_ARG" ]; then 50 | echo "vm.args needs to have a -setcookie parameter." 51 | exit 1 52 | fi 53 | 54 | # Identify the script name 55 | SCRIPT=`basename $0` 56 | 57 | # Parse out release and erts info 58 | START_ERL=`cat $RUNNER_BASE_DIR/releases/start_erl.data` 59 | ERTS_VSN=${START_ERL% *} 60 | APP_VSN=${START_ERL#* } 61 | 62 | # Add ERTS bin dir to our path 63 | ERTS_PATH=$RUNNER_BASE_DIR/erts-$ERTS_VSN/bin 64 | 65 | # Setup command to control the node 66 | NODETOOL="$ERTS_PATH/escript $ERTS_PATH/nodetool $NAME_ARG $COOKIE_ARG" 67 | 68 | run() 69 | { 70 | mod=$1 71 | shift 72 | cmd=$1 73 | shift 74 | 75 | # Make sure the local node IS running 76 | RES=`$NODETOOL ping` 77 | if [ "$RES" != "pong" ]; then 78 | echo "Node is not running!" 79 | exit 1 80 | fi 81 | 82 | $NODETOOL rpc $mod $cmd $@ 83 | } 84 | 85 | # Check the first argument for instructions 86 | case "$1" in 87 | join) 88 | shift 89 | run riak_pg_console join $@ 90 | ;; 91 | 92 | leave) 93 | shift 94 | run riak_pg_console leave $@ 95 | ;; 96 | 97 | remove) 98 | if [ $# -ne 2 ]; then 99 | echo "Usage: $SCRIPT remove " 100 | exit 1 101 | fi 102 | 103 | shift 104 | run riak_pg_console remove $@ 105 | ;; 106 | 107 | member_status) 108 | if [ $# -ne 1 ]; then 109 | echo "Usage: $SCRIPT member_status" 110 | exit 1 111 | fi 112 | 113 | shift 114 | run riak_core_console member_status $@ 115 | ;; 116 | 117 | ring_status) 118 | if [ $# -ne 1 ]; then 119 | echo "Usage: $SCRIPT ring_status" 120 | exit 1 121 | fi 122 | 123 | shift 124 | run riak_core_console ring_status $@ 125 | ;; 126 | 127 | services) 128 | $NODETOOL rpcterms riak_core_node_watcher services '' 129 | ;; 130 | 131 | wait-for-service) 132 | SVC=$2 133 | TARGETNODE=$3 134 | if [ $# -lt 3 ]; then 135 | echo "Usage: $SCRIPT wait-for-service " 136 | exit 1 137 | fi 138 | 139 | while (true); do 140 | # Make sure riak_core_node_watcher is up and running locally before trying to query it 141 | # to avoid ugly (but harmless) error messages 142 | NODEWATCHER=`$NODETOOL rpcterms erlang whereis "'riak_core_node_watcher'."` 143 | if [ "$NODEWATCHER" = "undefined" ]; then 144 | echo "$SVC is not up: node watcher is not running" 145 | continue 146 | fi 147 | 148 | # Get the list of services that are available on the requested node 149 | SERVICES=`$NODETOOL rpcterms riak_core_node_watcher services "'${TARGETNODE}'."` 150 | echo "$SERVICES" | grep "[[,]$SVC[],]" > /dev/null 2>&1 151 | if [ "X$?" = "X0" ]; then 152 | echo "$SVC is up" 153 | exit 0 154 | else 155 | echo "$SVC is not up: $SERVICES" 156 | fi 157 | sleep 3 158 | done 159 | ;; 160 | 161 | ringready) 162 | shift 163 | run riak_pg_console ringready $@ 164 | ;; 165 | 166 | *) 167 | echo "Usage: $SCRIPT { join | leave | ringready | remove |" 168 | echo " services | wait-for-service | member_status |" 169 | echo " ring_status }" 170 | exit 1 171 | ;; 172 | esac 173 | -------------------------------------------------------------------------------- /rel/files/vm.args: -------------------------------------------------------------------------------- 1 | ## Name of the node 2 | -name {{node}} 3 | 4 | ## Cookie for distributed erlang 5 | -setcookie {{cookie}} 6 | 7 | ## Heartbeat management; auto-restarts VM if it dies or becomes unresponsive 8 | ## (Disabled by default..use with caution!) 9 | ##-heart 10 | 11 | ## Enable kernel poll and a few async threads 12 | +K true 13 | +A 5 14 | 15 | ## Increase number of concurrent ports/sockets 16 | -env ERL_MAX_PORTS 4096 17 | 18 | ## Tweak GC to run more often 19 | -env ERL_FULLSWEEP_AFTER 10 20 | 21 | -------------------------------------------------------------------------------- /rel/gen_dev: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # 3 | # gen_dev dev4 vars.src vars 4 | # 5 | # Generate an overlay config for devNNN from vars.src and write to vars 6 | # 7 | 8 | NAME=$1 9 | TEMPLATE=$2 10 | VARFILE=$3 11 | 12 | ## Allocate 10 ports per node 13 | ## .7 - http 14 | 15 | NUMBER=${NAME##dev} 16 | BASE=$((10000 + 10 * $NUMBER)) 17 | WEBPORT=$(($BASE + 8)) 18 | HANDOFFPORT=$(($BASE + 9)) 19 | NODENAME="riak_pg$NUMBER@127.0.0.1" 20 | 21 | echo "Generating $NAME - node='$NODENAME' http=$WEBPORT handoff=$HANDOFFPORT" 22 | sed -e "s/@NODE@/$NODENAME/" \ 23 | -e "s/@WEBPORT@/$WEBPORT/" \ 24 | -e "s/@HANDOFFPORT@/$HANDOFFPORT/" < $TEMPLATE > $VARFILE 25 | -------------------------------------------------------------------------------- /rel/reltool.config: -------------------------------------------------------------------------------- 1 | {sys, [ 2 | {lib_dirs, ["../apps/", "../deps/"]}, 3 | {rel, "riak_pg", "1", 4 | [ 5 | kernel, 6 | stdlib, 7 | sasl, 8 | riak_pg 9 | ]}, 10 | {rel, "start_clean", "", 11 | [ 12 | kernel, 13 | stdlib 14 | ]}, 15 | {boot_rel, "riak_pg"}, 16 | {profile, embedded}, 17 | {excl_sys_filters, ["^bin/.*", 18 | "^erts.*/bin/(dialyzer|typer)"]}, 19 | {excl_archive_filters, [".*"]}, 20 | {app, sasl, [{incl_cond, include}]}, 21 | {app, riak_pg, [{incl_cond, include}]} 22 | ]}. 23 | 24 | {target_dir, "riak_pg"}. 25 | 26 | {overlay_vars, "vars.config"}. 27 | 28 | {overlay, [ 29 | {mkdir, "data/ring"}, 30 | {mkdir, "log/sasl"}, 31 | {copy, "files/erl", "\{\{erts_vsn\}\}/bin/erl"}, 32 | {copy, "files/nodetool", "\{\{erts_vsn\}\}/bin/nodetool"}, 33 | {template, "files/app.config", "etc/app.config"}, 34 | {template, "files/vm.args", "etc/vm.args"}, 35 | {template, "files/riak_pg", "bin/riak_pg"}, 36 | {template, "files/riak_pg-admin", "bin/riak_pg-admin"} 37 | ]}. 38 | -------------------------------------------------------------------------------- /rel/vars.config: -------------------------------------------------------------------------------- 1 | %% 2 | %% etc/app.config 3 | %% 4 | {ring_state_dir, "data/ring"}. 5 | {web_ip, "127.0.0.1"}. 6 | {web_port, "8098"}. 7 | {handoff_port, "8099"}. 8 | 9 | %% 10 | %% etc/vm.args 11 | %% 12 | {node, "riak_pg@127.0.0.1"}. 13 | {cookie, "riak_pg"}. 14 | -------------------------------------------------------------------------------- /rel/vars/dev_vars.config.src: -------------------------------------------------------------------------------- 1 | %% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- 2 | %% ex: ft=erlang ts=4 sw=4 et 3 | 4 | %% 5 | %% etc/app.config 6 | %% 7 | {ring_state_dir, "data/ring"}. 8 | {web_ip, "127.0.0.1"}. 9 | {web_port, @WEBPORT@}. 10 | {handoff_port, @HANDOFFPORT@}. 11 | 12 | %% 13 | %% etc/vm.args 14 | %% 15 | {node, "@NODE@"}. 16 | {cookie, "riak_pg"}. 17 | 18 | -------------------------------------------------------------------------------- /src/riak_pg.app.src: -------------------------------------------------------------------------------- 1 | %% -*- erlang -*- 2 | {application, riak_pg, 3 | [ 4 | {description, ""}, 5 | {vsn, "1"}, 6 | {registered, []}, 7 | {applications, [kernel, 8 | stdlib, 9 | sasl, 10 | riak_core]}, 11 | {mod, {riak_pg_app, []}}, 12 | {env, []} 13 | ]}. 14 | -------------------------------------------------------------------------------- /src/riak_pg.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright (c) 2013 Christopher Meiklejohn. All Rights Reserved. 4 | %% 5 | %% This file is provided to you under the Apache License, 6 | %% Version 2.0 (the "License"); you may not use this file 7 | %% except in compliance with the License. You may obtain 8 | %% a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, 13 | %% software distributed under the License is distributed on an 14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | %% KIND, either express or implied. See the License for the 16 | %% specific language governing permissions and limitations 17 | %% under the License. 18 | %% 19 | %% ------------------------------------------------------------------- 20 | %% @author Christopher Meiklejohn 21 | %% @copyright 2013 Christopher Meiklejohn. 22 | %% @doc Application. 23 | 24 | -module(riak_pg). 25 | -author('Christopher Meiklejohn '). 26 | 27 | -include("riak_pg.hrl"). 28 | -include_lib("riak_core/include/riak_core_vnode.hrl"). 29 | 30 | -define(TIMEOUT, 5000). 31 | 32 | -export([delete/1, 33 | join/2, 34 | leave/2, 35 | groups/0, 36 | members/1, 37 | local_members/1, 38 | connected_members/1]). 39 | 40 | -export([mk_reqid/0, 41 | wait_for_reqid/2]). 42 | 43 | %% Public API 44 | 45 | %% @doc Delete a group. 46 | -spec delete(term()) -> ok | {error, timeout}. 47 | delete(Group) -> 48 | {ok, ReqId} = riak_pg_delete_fsm:delete(Group), 49 | wait_for_reqid(ReqId, ?TIMEOUT). 50 | 51 | %% @doc Join pid to group. 52 | -spec join(term(), pid()) -> ok | {error, timeout}. 53 | join(Group, Pid) -> 54 | {ok, ReqId} = riak_pg_join_fsm:join(Group, Pid), 55 | wait_for_reqid(ReqId, ?TIMEOUT). 56 | 57 | %% @doc Remove pid from group. 58 | -spec leave(term(), pid()) -> ok | {error, timeout}. 59 | leave(Group, Pid) -> 60 | {ok, ReqId} = riak_pg_leave_fsm:leave(Group, Pid), 61 | wait_for_reqid(ReqId, ?TIMEOUT). 62 | 63 | %% @doc Return a listing of all registered groups. 64 | %% @todo 65 | -spec groups() -> ok. 66 | groups() -> 67 | {ok, ReqId} = riak_pg_groups_fsm:groups(), 68 | wait_for_reqid(ReqId, ?TIMEOUT). 69 | 70 | %% @doc Return a listing of members of a particular group. 71 | -spec members(term()) -> {ok, list(pid())} | {error, timeout}. 72 | members(Group) -> 73 | {ok, ReqId} = riak_pg_members_fsm:members(Group), 74 | wait_for_reqid(ReqId, ?TIMEOUT). 75 | 76 | %% @doc Return a listing of local members of a particular group. 77 | -spec local_members(term()) -> {ok, list(pid())} | {error, timeout}. 78 | local_members(Group) -> 79 | {ok, ReqId} = riak_pg_members_fsm:members(Group), 80 | case wait_for_reqid(ReqId, ?TIMEOUT) of 81 | {ok, Members} -> 82 | LocalMembers = lists:filter(fun(Pid) -> 83 | node(Pid) =:= node() end, Members), 84 | {ok, LocalMembers}; 85 | {error, Error} -> 86 | {error, Error} 87 | end. 88 | 89 | %% @doc Return a listing of connected members of a particular group. 90 | -spec connected_members(term()) -> {ok, list(pid())} | {error, timeout}. 91 | connected_members(Group) -> 92 | {ok, ReqId} = riak_pg_members_fsm:members(Group), 93 | case wait_for_reqid(ReqId, ?TIMEOUT) of 94 | {ok, Members} -> 95 | ConnectedMembers = lists:filter(fun(Pid) -> 96 | lists:member(node(Pid), nodes()) end, Members), 97 | {ok, ConnectedMembers}; 98 | {error, Error} -> 99 | {error, Error} 100 | end. 101 | 102 | %%%=================================================================== 103 | %%% Internal Functions 104 | %%%=================================================================== 105 | 106 | %% @doc Generate a request id. 107 | mk_reqid() -> 108 | erlang:phash2(erlang:now()). 109 | 110 | %% @doc Wait for a response. 111 | wait_for_reqid(ReqID, Timeout) -> 112 | receive 113 | {ReqID, ok} -> 114 | ok; 115 | {ReqID, ok, Val} -> 116 | {ok, Val} 117 | after Timeout -> 118 | {error, timeout} 119 | end. 120 | -------------------------------------------------------------------------------- /src/riak_pg_app.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright (c) 2013 Christopher Meiklejohn. All Rights Reserved. 4 | %% 5 | %% This file is provided to you under the Apache License, 6 | %% Version 2.0 (the "License"); you may not use this file 7 | %% except in compliance with the License. You may obtain 8 | %% a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, 13 | %% software distributed under the License is distributed on an 14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | %% KIND, either express or implied. See the License for the 16 | %% specific language governing permissions and limitations 17 | %% under the License. 18 | %% 19 | %% ------------------------------------------------------------------- 20 | %% @author Christopher Meiklejohn 21 | %% @copyright 2013 Christopher Meiklejohn. 22 | %% @doc Application. 23 | 24 | -module(riak_pg_app). 25 | -author('Christopher Meiklejohn '). 26 | 27 | -behaviour(application). 28 | 29 | %% Application callbacks 30 | -export([start/2, 31 | stop/1]). 32 | 33 | %% =================================================================== 34 | %% Application callbacks 35 | %% =================================================================== 36 | 37 | start(_StartType, _StartArgs) -> 38 | case riak_pg_sup:start_link() of 39 | {ok, Pid} -> 40 | ok = riak_core:register(riak_pg, [{vnode_module, riak_pg_vnode}]), 41 | ok = riak_core_node_watcher:service_up(riak_pg, self()), 42 | 43 | ok = riak_core_ring_events:add_guarded_handler( 44 | riak_pg_ring_event_handler, []), 45 | 46 | ok = riak_core_node_watcher_events:add_guarded_handler( 47 | riak_pg_node_event_handler, []), 48 | 49 | {ok, Pid}; 50 | {error, Reason} -> 51 | {error, Reason} 52 | end. 53 | 54 | stop(_State) -> 55 | ok. 56 | -------------------------------------------------------------------------------- /src/riak_pg_console.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright (c) 2013 Christopher Meiklejohn. All Rights Reserved. 4 | %% 5 | %% This file is provided to you under the Apache License, 6 | %% Version 2.0 (the "License"); you may not use this file 7 | %% except in compliance with the License. You may obtain 8 | %% a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, 13 | %% software distributed under the License is distributed on an 14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | %% KIND, either express or implied. See the License for the 16 | %% specific language governing permissions and limitations 17 | %% under the License. 18 | %% 19 | %% ------------------------------------------------------------------- 20 | %% @author Christopher Meiklejohn 21 | %% @copyright 2013 Christopher Meiklejohn. 22 | %% @doc Console cluster administrator. 23 | 24 | -module(riak_pg_console). 25 | -author('Christopher Meiklejohn '). 26 | 27 | -export([join/1, 28 | leave/1, 29 | remove/1, 30 | ringready/1]). 31 | 32 | join([NodeStr]) -> 33 | try 34 | case riak_core:join(NodeStr) of 35 | ok -> 36 | io:format("Sent join request to ~s~n", [NodeStr]), 37 | ok; 38 | {error, not_reachable} -> 39 | io:format("Node ~s is not reachable!~n", [NodeStr]), 40 | error; 41 | {error, different_ring_sizes} -> 42 | io:format("Failed: ~s has a different ring_creation_size~n", 43 | [NodeStr]), 44 | error; 45 | {error, unable_to_get_join_ring} -> 46 | io:format("Failed: Unable to get ring from ~s~n", [NodeStr]), 47 | error; 48 | {error, not_single_node} -> 49 | io:format("Failed: This node is already a member of a " 50 | "cluster~n"), 51 | error; 52 | {error, _} -> 53 | io:format("Join failed. Try again in a few moments.~n", []), 54 | error 55 | end 56 | catch 57 | Exception:Reason -> 58 | lager:error("Join failed ~p:~p", [Exception, Reason]), 59 | io:format("Join failed, see log for details~n"), 60 | error 61 | end. 62 | 63 | leave([]) -> 64 | try 65 | case riak_core:leave() of 66 | ok -> 67 | io:format("Success: ~p will shutdown after handing off " 68 | "its data~n", [node()]), 69 | ok; 70 | {error, already_leaving} -> 71 | io:format("~p is already in the process of leaving the " 72 | "cluster.~n", [node()]), 73 | ok; 74 | {error, not_member} -> 75 | io:format("Failed: ~p is not a member of the cluster.~n", 76 | [node()]), 77 | error; 78 | {error, only_member} -> 79 | io:format("Failed: ~p is the only member.~n", [node()]), 80 | error 81 | end 82 | catch 83 | Exception:Reason -> 84 | lager:error("Leave failed ~p:~p", [Exception, Reason]), 85 | io:format("Leave failed, see log for details~n"), 86 | error 87 | end. 88 | 89 | remove([Node]) -> 90 | try 91 | case riak_core:remove(list_to_atom(Node)) of 92 | ok -> 93 | io:format("Success: ~p removed from the cluster~n", [Node]), 94 | ok; 95 | {error, not_member} -> 96 | io:format("Failed: ~p is not a member of the cluster.~n", 97 | [Node]), 98 | error; 99 | {error, only_member} -> 100 | io:format("Failed: ~p is the only member.~n", [Node]), 101 | error 102 | end 103 | catch 104 | Exception:Reason -> 105 | lager:error("Remove failed ~p:~p", [Exception, Reason]), 106 | io:format("Remove failed, see log for details~n"), 107 | error 108 | end. 109 | 110 | ringready([]) -> 111 | try 112 | case riak_core_status:ringready() of 113 | {ok, Nodes} -> 114 | io:format("TRUE All nodes agree on the ring ~p\n", [Nodes]); 115 | {error, {different_owners, N1, N2}} -> 116 | io:format("FALSE Node ~p and ~p list different partition owners\n", [N1, N2]), 117 | error; 118 | {error, {nodes_down, Down}} -> 119 | io:format("FALSE ~p down. All nodes need to be up to check.\n", [Down]), 120 | error 121 | end 122 | catch 123 | Exception:Reason -> 124 | lager:error("Ringready failed ~p:~p", [Exception, 125 | Reason]), 126 | io:format("Ringready failed, see log for details~n"), 127 | error 128 | end. 129 | -------------------------------------------------------------------------------- /src/riak_pg_delete_fsm.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright (c) 2013 Christopher Meiklejohn. All Rights Reserved. 4 | %% 5 | %% This file is provided to you under the Apache License, 6 | %% Version 2.0 (the "License"); you may not use this file 7 | %% except in compliance with the License. You may obtain 8 | %% a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, 13 | %% software distributed under the License is distributed on an 14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | %% KIND, either express or implied. See the License for the 16 | %% specific language governing permissions and limitations 17 | %% under the License. 18 | %% 19 | %% ------------------------------------------------------------------- 20 | %% @author Christopher Meiklejohn 21 | %% @copyright 2013 Christopher Meiklejohn. 22 | %% @doc Delete FSM. 23 | 24 | -module(riak_pg_delete_fsm). 25 | -author('Christopher Meiklejohn '). 26 | 27 | -behaviour(gen_fsm). 28 | 29 | -include("riak_pg.hrl"). 30 | 31 | %% API 32 | -export([start_link/3, 33 | delete/1]). 34 | 35 | %% Callbacks 36 | -export([init/1, 37 | code_change/4, 38 | handle_event/3, 39 | handle_info/3, 40 | handle_sync_event/4, 41 | terminate/3]). 42 | 43 | %% States 44 | -export([prepare/2, 45 | execute/2, 46 | waiting/2]). 47 | 48 | -record(state, {preflist, 49 | req_id, 50 | coordinator, 51 | from, 52 | group, 53 | responses}). 54 | 55 | %%%=================================================================== 56 | %%% API 57 | %%%=================================================================== 58 | 59 | start_link(ReqId, From, Group) -> 60 | gen_fsm:start_link(?MODULE, [ReqId, From, Group], []). 61 | 62 | %% @doc Delete a group. 63 | delete(Group) -> 64 | ReqId = riak_pg:mk_reqid(), 65 | _ = riak_pg_delete_fsm_sup:start_child([ReqId, self(), Group]), 66 | {ok, ReqId}. 67 | 68 | %%%=================================================================== 69 | %%% Callbacks 70 | %%%=================================================================== 71 | 72 | handle_info(_Info, _StateName, StateData) -> 73 | {stop, badmsg, StateData}. 74 | 75 | handle_event(_Event, _StateName, StateData) -> 76 | {stop, badmsg, StateData}. 77 | 78 | handle_sync_event(_Event, _From, _StateName, StateData) -> 79 | {stop, badmsg, StateData}. 80 | 81 | code_change(_OldVsn, StateName, State, _Extra) -> 82 | {ok, StateName, State}. 83 | 84 | terminate(_Reason, _SN, _SD) -> 85 | ok. 86 | 87 | %%%=================================================================== 88 | %%% States 89 | %%%=================================================================== 90 | 91 | %% @doc Initialize the request. 92 | init([ReqId, From, Group]) -> 93 | State = #state{preflist=undefined, 94 | req_id=ReqId, 95 | coordinator=node(), 96 | from=From, 97 | group=Group, 98 | responses=0}, 99 | {ok, prepare, State, 0}. 100 | 101 | %% @doc Prepare request by retrieving the preflist. 102 | prepare(timeout, #state{group=Group}=State) -> 103 | DocIdx = riak_core_util:chash_key({<<"pg">>, Group}), 104 | Preflist = riak_core_apl:get_primary_apl(DocIdx, ?N, riak_pg), 105 | Preflist2 = [{Index, Node} || {{Index, Node}, _Type} <- Preflist], 106 | {next_state, execute, State#state{preflist=Preflist2}, 0}. 107 | 108 | %% @doc Execute the request. 109 | execute(timeout, #state{preflist=Preflist, 110 | req_id=ReqId, 111 | coordinator=Coordinator, 112 | group=Group}=State) -> 113 | riak_pg_vnode:delete(Preflist, {ReqId, Coordinator}, Group), 114 | {next_state, waiting, State}. 115 | 116 | %% @doc Attempt to write to every single node responsible for this 117 | %% group. 118 | waiting({ok, ReqId}, #state{responses=Responses0, from=From}=State0) -> 119 | Responses = Responses0 + 1, 120 | State = State0#state{responses=Responses}, 121 | case Responses =:= ?W of 122 | true -> 123 | From ! {ReqId, ok}, 124 | {stop, normal, State}; 125 | false -> 126 | {next_state, waiting, State} 127 | end. 128 | 129 | %%%=================================================================== 130 | %%% Internal Functions 131 | %%%=================================================================== 132 | -------------------------------------------------------------------------------- /src/riak_pg_delete_fsm_sup.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright (c) 2013 Christopher Meiklejohn. All Rights Reserved. 4 | %% 5 | %% This file is provided to you under the Apache License, 6 | %% Version 2.0 (the "License"); you may not use this file 7 | %% except in compliance with the License. You may obtain 8 | %% a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, 13 | %% software distributed under the License is distributed on an 14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | %% KIND, either express or implied. See the License for the 16 | %% specific language governing permissions and limitations 17 | %% under the License. 18 | %% 19 | %% ------------------------------------------------------------------- 20 | %% @author Christopher Meiklejohn 21 | %% @copyright 2013 Christopher Meiklejohn. 22 | %% @doc Supervisor for the delete requests. 23 | 24 | -module(riak_pg_delete_fsm_sup). 25 | -author('Christopher Meiklejohn '). 26 | 27 | -behaviour(supervisor). 28 | 29 | %% API 30 | -export([start_link/0, 31 | start_child/1, 32 | terminate_child/2]). 33 | 34 | %% Supervisor callbacks 35 | -export([init/1]). 36 | 37 | %% =================================================================== 38 | %% API functions 39 | %% =================================================================== 40 | 41 | %% @doc API for starting the supervisor. 42 | start_link() -> 43 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 44 | 45 | %% @doc Start a child. 46 | start_child(Args) -> 47 | supervisor:start_child(?MODULE, Args). 48 | 49 | %% @doc Stop a child immediately 50 | terminate_child(Supervisor, Pid) -> 51 | supervisor:terminate_child(Supervisor, Pid). 52 | 53 | %% =================================================================== 54 | %% Supervisor callbacks 55 | %% =================================================================== 56 | 57 | %% @doc supervisor callback. 58 | init([]) -> 59 | Spec = {riak_pg_delete_fsm, 60 | {riak_pg_delete_fsm, start_link, []}, 61 | temporary, 5000, worker, [riak_pg_delete_fsm]}, 62 | 63 | {ok, {{simple_one_for_one, 10, 10}, [Spec]}}. 64 | -------------------------------------------------------------------------------- /src/riak_pg_groups_fsm.erl: -------------------------------------------------------------------------------- 1 | -module(riak_pg_groups_fsm). 2 | 3 | -behaviour(riak_core_coverage_fsm). 4 | 5 | -include("riak_pg.hrl"). 6 | 7 | -export([init/2, 8 | process_results/2, 9 | finish/2, 10 | groups/0]). 11 | 12 | -record(state, {results = [],from}). 13 | -define(TIMEOUT, 10000). 14 | 15 | groups() -> 16 | ReqId = riak_pg:mk_reqid(), 17 | riak_pg_groups_fsm_sup:start_child([{raw, ReqId, self()},[?TIMEOUT]]), 18 | {ok, ReqId}. 19 | 20 | init(From,[Timeout]) -> 21 | { 22 | groups, 23 | all, 24 | ?N, 25 | ?R, 26 | riak_pg, 27 | riak_pg_vnode_master, 28 | Timeout, 29 | #state{from=From} 30 | }. 31 | 32 | process_results(done, State) -> 33 | {done, State}; 34 | process_results(Groups, StateData=#state{results=Results}) -> 35 | {done, StateData#state{results=lists:append(Groups, Results)}}; 36 | process_results({error, Reason}, _State) -> 37 | {error, Reason}; 38 | process_results(Message, State) -> 39 | lager:info("Unhandled result: ~p", [Message]), 40 | {ok, State}. 41 | 42 | finish({error, Error}, StateData=#state{from={raw, ReqId, ClientPid}}) -> 43 | lager:info("Error in coverage request: ~p~n", [Error]), 44 | ClientPid ! {ReqId, Error}, 45 | {stop, normal, StateData}; 46 | 47 | finish(clean, StateData=#state{results=Results,from={raw, ReqId, ClientPid}}) -> 48 | List = lists:usort(Results), 49 | ClientPid ! {ReqId, ok, List}, 50 | {stop, normal, StateData}; 51 | 52 | finish(Message, StateData) -> 53 | lager:info("Unhandled finish: ~p~n", [Message]), 54 | {stop, normal, StateData}. 55 | -------------------------------------------------------------------------------- /src/riak_pg_groups_fsm_sup.erl: -------------------------------------------------------------------------------- 1 | -module('riak_pg_groups_fsm_sup'). 2 | 3 | -behaviour(supervisor). 4 | 5 | -export([start_child/1,terminate_child/2]). 6 | -export([start_link/0]). 7 | -export([init/1]). 8 | 9 | start_link() -> 10 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 11 | 12 | start_child(Args) -> 13 | supervisor:start_child(?MODULE, Args). 14 | 15 | terminate_child(Supervisor, Pid) -> 16 | supervisor:terminate_child(Supervisor, Pid). 17 | 18 | init([]) -> 19 | GroupsFsmSpec = {undefined, 20 | {riak_core_coverage_fsm, start_link, [riak_pg_groups_fsm]}, 21 | temporary, 5000, worker, [riak_pg_groups_fsm]}, 22 | 23 | {ok, {{simple_one_for_one, 10, 10}, [GroupsFsmSpec]}}. 24 | -------------------------------------------------------------------------------- /src/riak_pg_join_fsm.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright (c) 2013 Christopher Meiklejohn. All Rights Reserved. 4 | %% 5 | %% This file is provided to you under the Apache License, 6 | %% Version 2.0 (the "License"); you may not use this file 7 | %% except in compliance with the License. You may obtain 8 | %% a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, 13 | %% software distributed under the License is distributed on an 14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | %% KIND, either express or implied. See the License for the 16 | %% specific language governing permissions and limitations 17 | %% under the License. 18 | %% 19 | %% ------------------------------------------------------------------- 20 | %% @author Christopher Meiklejohn 21 | %% @copyright 2013 Christopher Meiklejohn. 22 | %% @doc Join FSM. 23 | 24 | -module(riak_pg_join_fsm). 25 | -author('Christopher Meiklejohn '). 26 | 27 | -behaviour(gen_fsm). 28 | 29 | -include("riak_pg.hrl"). 30 | 31 | %% API 32 | -export([start_link/4, 33 | join/2]). 34 | 35 | %% Callbacks 36 | -export([init/1, 37 | code_change/4, 38 | handle_event/3, 39 | handle_info/3, 40 | handle_sync_event/4, 41 | terminate/3]). 42 | 43 | %% States 44 | -export([prepare/2, 45 | execute/2, 46 | waiting/2]). 47 | 48 | -record(state, {preflist, 49 | req_id, 50 | coordinator, 51 | from, 52 | group, 53 | pid, 54 | responses}). 55 | 56 | %%%=================================================================== 57 | %%% API 58 | %%%=================================================================== 59 | 60 | start_link(ReqId, From, Group, Pid) -> 61 | gen_fsm:start_link(?MODULE, [ReqId, From, Group, Pid], []). 62 | 63 | %% @doc Join a pid to a group. 64 | join(Group, Pid) -> 65 | ReqId = riak_pg:mk_reqid(), 66 | _ = riak_pg_join_fsm_sup:start_child([ReqId, self(), Group, Pid]), 67 | {ok, ReqId}. 68 | 69 | %%%=================================================================== 70 | %%% Callbacks 71 | %%%=================================================================== 72 | 73 | handle_info(_Info, _StateName, StateData) -> 74 | {stop, badmsg, StateData}. 75 | 76 | handle_event(_Event, _StateName, StateData) -> 77 | {stop, badmsg, StateData}. 78 | 79 | handle_sync_event(_Event, _From, _StateName, StateData) -> 80 | {stop, badmsg, StateData}. 81 | 82 | code_change(_OldVsn, StateName, State, _Extra) -> 83 | {ok, StateName, State}. 84 | 85 | terminate(_Reason, _SN, _SD) -> 86 | ok. 87 | 88 | %%%=================================================================== 89 | %%% States 90 | %%%=================================================================== 91 | 92 | %% @doc Initialize the request. 93 | init([ReqId, From, Group, Pid]) -> 94 | State = #state{preflist=undefined, 95 | req_id=ReqId, 96 | coordinator=node(), 97 | from=From, 98 | group=Group, 99 | pid=Pid, 100 | responses=0}, 101 | {ok, prepare, State, 0}. 102 | 103 | %% @doc Prepare request by retrieving the preflist. 104 | prepare(timeout, #state{group=Group}=State) -> 105 | DocIdx = riak_core_util:chash_key({<<"pg">>, Group}), 106 | Preflist = riak_core_apl:get_primary_apl(DocIdx, ?N, riak_pg), 107 | Preflist2 = [{Index, Node} || {{Index, Node}, _Type} <- Preflist], 108 | {next_state, execute, State#state{preflist=Preflist2}, 0}. 109 | 110 | %% @doc Execute the request. 111 | execute(timeout, #state{preflist=Preflist, 112 | req_id=ReqId, 113 | coordinator=Coordinator, 114 | group=Group, 115 | pid=Pid}=State) -> 116 | riak_pg_vnode:join(Preflist, {ReqId, Coordinator}, Group, Pid), 117 | {next_state, waiting, State}. 118 | 119 | %% @doc Attempt to write to every single node responsible for this 120 | %% group. 121 | waiting({ok, ReqId}, #state{responses=Responses0, from=From}=State0) -> 122 | Responses = Responses0 + 1, 123 | State = State0#state{responses=Responses}, 124 | case Responses =:= ?W of 125 | true -> 126 | From ! {ReqId, ok}, 127 | {stop, normal, State}; 128 | false -> 129 | {next_state, waiting, State} 130 | end. 131 | 132 | %%%=================================================================== 133 | %%% Internal Functions 134 | %%%=================================================================== 135 | -------------------------------------------------------------------------------- /src/riak_pg_join_fsm_sup.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright (c) 2013 Christopher Meiklejohn. All Rights Reserved. 4 | %% 5 | %% This file is provided to you under the Apache License, 6 | %% Version 2.0 (the "License"); you may not use this file 7 | %% except in compliance with the License. You may obtain 8 | %% a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, 13 | %% software distributed under the License is distributed on an 14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | %% KIND, either express or implied. See the License for the 16 | %% specific language governing permissions and limitations 17 | %% under the License. 18 | %% 19 | %% ------------------------------------------------------------------- 20 | %% @author Christopher Meiklejohn 21 | %% @copyright 2013 Christopher Meiklejohn. 22 | %% @doc Supervisor for the join requests. 23 | 24 | -module(riak_pg_join_fsm_sup). 25 | -author('Christopher Meiklejohn '). 26 | 27 | -behaviour(supervisor). 28 | 29 | %% API 30 | -export([start_link/0, 31 | start_child/1, 32 | terminate_child/2]). 33 | 34 | %% Supervisor callbacks 35 | -export([init/1]). 36 | 37 | %% =================================================================== 38 | %% API functions 39 | %% =================================================================== 40 | 41 | %% @doc API for starting the supervisor. 42 | start_link() -> 43 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 44 | 45 | %% @doc Start a child. 46 | start_child(Args) -> 47 | supervisor:start_child(?MODULE, Args). 48 | 49 | %% @doc Stop a child immediately 50 | terminate_child(Supervisor, Pid) -> 51 | supervisor:terminate_child(Supervisor, Pid). 52 | 53 | %% =================================================================== 54 | %% Supervisor callbacks 55 | %% =================================================================== 56 | 57 | %% @doc supervisor callback. 58 | init([]) -> 59 | Spec = {riak_pg_join_fsm, 60 | {riak_pg_join_fsm, start_link, []}, 61 | temporary, 5000, worker, [riak_pg_join_fsm]}, 62 | 63 | {ok, {{simple_one_for_one, 10, 10}, [Spec]}}. 64 | -------------------------------------------------------------------------------- /src/riak_pg_leave_fsm.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright (c) 2013 Christopher Meiklejohn. All Rights Reserved. 4 | %% 5 | %% This file is provided to you under the Apache License, 6 | %% Version 2.0 (the "License"); you may not use this file 7 | %% except in compliance with the License. You may obtain 8 | %% a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, 13 | %% software distributed under the License is distributed on an 14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | %% KIND, either express or implied. See the License for the 16 | %% specific language governing permissions and limitations 17 | %% under the License. 18 | %% 19 | %% ------------------------------------------------------------------- 20 | %% @author Christopher Meiklejohn 21 | %% @copyright 2013 Christopher Meiklejohn. 22 | %% @doc Leave FSM. 23 | 24 | -module(riak_pg_leave_fsm). 25 | -author('Christopher Meiklejohn '). 26 | 27 | -behaviour(gen_fsm). 28 | 29 | -include("riak_pg.hrl"). 30 | 31 | %% API 32 | -export([start_link/4, 33 | leave/2]). 34 | 35 | %% Callbacks 36 | -export([init/1, 37 | code_change/4, 38 | handle_event/3, 39 | handle_info/3, 40 | handle_sync_event/4, 41 | terminate/3]). 42 | 43 | %% States 44 | -export([prepare/2, 45 | execute/2, 46 | waiting/2]). 47 | 48 | -record(state, {preflist, 49 | req_id, 50 | coordinator, 51 | from, 52 | group, 53 | pid, 54 | responses}). 55 | 56 | %%%=================================================================== 57 | %%% API 58 | %%%=================================================================== 59 | 60 | start_link(ReqId, From, Group, Pid) -> 61 | gen_fsm:start_link(?MODULE, [ReqId, From, Group, Pid], []). 62 | 63 | %% @doc Leave this process group. 64 | leave(Group, Pid) -> 65 | ReqId = riak_pg:mk_reqid(), 66 | _ = riak_pg_leave_fsm_sup:start_child([ReqId, self(), Group, Pid]), 67 | {ok, ReqId}. 68 | 69 | %%%=================================================================== 70 | %%% Callbacks 71 | %%%=================================================================== 72 | 73 | handle_info(_Info, _StateName, StateData) -> 74 | {stop, badmsg, StateData}. 75 | 76 | handle_event(_Event, _StateName, StateData) -> 77 | {stop, badmsg, StateData}. 78 | 79 | handle_sync_event(_Event, _From, _StateName, StateData) -> 80 | {stop, badmsg, StateData}. 81 | 82 | code_change(_OldVsn, StateName, State, _Extra) -> 83 | {ok, StateName, State}. 84 | 85 | terminate(_Reason, _SN, _SD) -> 86 | ok. 87 | 88 | %%%=================================================================== 89 | %%% States 90 | %%%=================================================================== 91 | 92 | %% @doc Initialize the request. 93 | init([ReqId, From, Group, Pid]) -> 94 | State = #state{preflist=undefined, 95 | req_id=ReqId, 96 | coordinator=node(), 97 | from=From, 98 | group=Group, 99 | pid=Pid, 100 | responses=0}, 101 | {ok, prepare, State, 0}. 102 | 103 | %% @doc Prepare request by retrieving the preflist. 104 | prepare(timeout, #state{group=Group}=State) -> 105 | DocIdx = riak_core_util:chash_key({<<"pg">>, Group}), 106 | Preflist = riak_core_apl:get_primary_apl(DocIdx, ?N, riak_pg), 107 | Preflist2 = [{Index, Node} || {{Index, Node}, _Type} <- Preflist], 108 | {next_state, execute, State#state{preflist=Preflist2}, 0}. 109 | 110 | %% @doc Execute the request. 111 | execute(timeout, #state{preflist=Preflist, 112 | req_id=ReqId, 113 | coordinator=Coordinator, 114 | group=Group, 115 | pid=Pid}=State) -> 116 | riak_pg_vnode:leave(Preflist, {ReqId, Coordinator}, Group, Pid), 117 | {next_state, waiting, State}. 118 | 119 | %% @doc Attempt to write to every single node responsible for this 120 | %% group. 121 | waiting({ok, ReqId}, #state{responses=Responses0, from=From}=State0) -> 122 | Responses = Responses0 + 1, 123 | State = State0#state{responses=Responses}, 124 | case Responses =:= ?W of 125 | true -> 126 | From ! {ReqId, ok}, 127 | {stop, normal, State}; 128 | false -> 129 | {next_state, waiting, State} 130 | end. 131 | 132 | %%%=================================================================== 133 | %%% Internal Functions 134 | %%%=================================================================== 135 | -------------------------------------------------------------------------------- /src/riak_pg_leave_fsm_sup.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright (c) 2013 Christopher Meiklejohn. All Rights Reserved. 4 | %% 5 | %% This file is provided to you under the Apache License, 6 | %% Version 2.0 (the "License"); you may not use this file 7 | %% except in compliance with the License. You may obtain 8 | %% a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, 13 | %% software distributed under the License is distributed on an 14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | %% KIND, either express or implied. See the License for the 16 | %% specific language governing permissions and limitations 17 | %% under the License. 18 | %% 19 | %% ------------------------------------------------------------------- 20 | %% @author Christopher Meiklejohn 21 | %% @copyright 2013 Christopher Meiklejohn. 22 | %% @doc Supervisor for the leave requests. 23 | 24 | -module(riak_pg_leave_fsm_sup). 25 | -author('Christopher Meiklejohn '). 26 | 27 | -behaviour(supervisor). 28 | 29 | %% API 30 | -export([start_link/0, 31 | start_child/1, 32 | terminate_child/2]). 33 | 34 | %% Supervisor callbacks 35 | -export([init/1]). 36 | 37 | %% =================================================================== 38 | %% API functions 39 | %% =================================================================== 40 | 41 | %% @doc API for starting the supervisor. 42 | start_link() -> 43 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 44 | 45 | %% @doc Start a child. 46 | start_child(Args) -> 47 | supervisor:start_child(?MODULE, Args). 48 | 49 | %% @doc Stop a child immediately 50 | terminate_child(Supervisor, Pid) -> 51 | supervisor:terminate_child(Supervisor, Pid). 52 | 53 | %% =================================================================== 54 | %% Supervisor callbacks 55 | %% =================================================================== 56 | 57 | %% @doc supervisor callback. 58 | init([]) -> 59 | Spec = {riak_pg_leave_fsm, 60 | {riak_pg_leave_fsm, start_link, []}, 61 | temporary, 5000, worker, [riak_pg_leave_fsm]}, 62 | 63 | {ok, {{simple_one_for_one, 10, 10}, [Spec]}}. 64 | -------------------------------------------------------------------------------- /src/riak_pg_members_fsm.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright (c) 2013 Christopher Meiklejohn, 2015 Mark Steele All Rights Reserved. 4 | %% 5 | %% This file is provided to you under the Apache License, 6 | %% Version 2.0 (the "License"); you may not use this file 7 | %% except in compliance with the License. You may obtain 8 | %% a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, 13 | %% software distributed under the License is distributed on an 14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | %% KIND, either express or implied. See the License for the 16 | %% specific language governing permissions and limitations 17 | %% under the License. 18 | %% 19 | %% ------------------------------------------------------------------- 20 | %% @author Christopher Meiklejohn 21 | %% @author Mark Steele 22 | %% @copyright 2013 Christopher Meiklejohn. 23 | %% @copryright 2015 Mark Steele. 24 | %% @doc Members FSM. 25 | 26 | -module(riak_pg_members_fsm). 27 | -author('Christopher Meiklejohn '). 28 | -author('Mark Steele '). 29 | 30 | -behaviour(gen_fsm). 31 | 32 | -include("riak_pg.hrl"). 33 | 34 | %% API 35 | -export([start_link/3, 36 | members/1]). 37 | 38 | %% Callbacks 39 | -export([init/1, 40 | code_change/4, 41 | handle_event/3, 42 | handle_info/3, 43 | handle_sync_event/4, 44 | terminate/3]). 45 | 46 | %% States 47 | -export([prepare/2, 48 | execute/2, 49 | waiting/2, 50 | waiting_n/2, 51 | finalize/2]). 52 | 53 | -record(state, {preflist, 54 | req_id, 55 | coordinator, 56 | from, 57 | group, 58 | groups, 59 | num_responses, 60 | replies, 61 | prune}). 62 | 63 | %%%=================================================================== 64 | %%% API 65 | %%%=================================================================== 66 | 67 | start_link(ReqId, From, Group) -> 68 | gen_fsm:start_link(?MODULE, [ReqId, From, Group], []). 69 | 70 | %% @doc Get members. 71 | members(Group) -> 72 | ReqId = riak_pg:mk_reqid(), 73 | _ = riak_pg_members_fsm_sup:start_child([ReqId, self(), Group]), 74 | {ok, ReqId}. 75 | 76 | %%%=================================================================== 77 | %%% Callbacks 78 | %%%=================================================================== 79 | 80 | handle_info(_Info, _StateName, StateData) -> 81 | {stop, badmsg, StateData}. 82 | 83 | handle_event(_Event, _StateName, StateData) -> 84 | {stop, badmsg, StateData}. 85 | 86 | handle_sync_event(_Event, _From, _StateName, StateData) -> 87 | {stop, badmsg, StateData}. 88 | 89 | code_change(_OldVsn, StateName, State, _Extra) -> 90 | {ok, StateName, State}. 91 | 92 | terminate(_Reason, _SN, _SD) -> 93 | ok. 94 | 95 | %%%=================================================================== 96 | %%% States 97 | %%%=================================================================== 98 | 99 | %% @doc Initialize the request. 100 | init([ReqId, From, Group]) -> 101 | State = #state{preflist=undefined, 102 | req_id=ReqId, 103 | coordinator=node(), 104 | from=From, 105 | group=Group, 106 | num_responses=0, 107 | replies=[]}, 108 | {ok, prepare, State, 0}. 109 | 110 | %% @doc Prepare request by retrieving the preflist. 111 | prepare(timeout, #state{group=Group}=State) -> 112 | DocIdx = riak_core_util:chash_key({<<"pg">>, Group}), 113 | Preflist = riak_core_apl:get_primary_apl(DocIdx, ?N, riak_pg), 114 | Preflist2 = [{Index, Node} || {{Index, Node}, _Type} <- Preflist], 115 | {next_state, execute, State#state{preflist=Preflist2}, 0}. 116 | 117 | %% @doc Execute the request. 118 | execute(timeout, #state{preflist=Preflist, 119 | req_id=ReqId, 120 | coordinator=Coordinator}=State) -> 121 | riak_pg_vnode:members(Preflist, {ReqId, Coordinator}), 122 | {next_state, waiting, State}. 123 | 124 | %% @doc Pull a unique list of memberships from replicas, and 125 | %% relay the message to it. 126 | waiting({ok, _ReqId, IndexNode, Reply}, 127 | #state{from=From, 128 | req_id=ReqId, 129 | group = Group, 130 | num_responses=NumResponses0, 131 | replies=Replies0}=State0) -> 132 | NumResponses = NumResponses0 + 1, 133 | Replies = [{IndexNode, Reply}|Replies0], 134 | State = State0#state{num_responses=NumResponses, replies=Replies}, 135 | 136 | case NumResponses =:= ?R of 137 | true -> 138 | {Prune, Pids} = merge_pids(Replies, Group), 139 | From ! {ReqId, ok, Pids}, 140 | {next_state, waiting_n, State#state{prune = Prune}}; 141 | false -> 142 | {next_state, waiting, State} 143 | end. 144 | 145 | %% @doc Wait for the remainder of responses from replicas. 146 | waiting_n({ok, _ReqId, IndexNode, Reply}, 147 | #state{num_responses=NumResponses0, 148 | replies=Replies0}=State0) -> 149 | NumResponses = NumResponses0 + 1, 150 | Replies = [{IndexNode, Reply}|Replies0], 151 | State = State0#state{num_responses=NumResponses, replies=Replies}, 152 | 153 | case NumResponses =:= ?N of 154 | true -> 155 | {next_state, finalize, State, 0}; 156 | false -> 157 | {next_state, waiting_n, State} 158 | end. 159 | 160 | %% @doc Perform read repair. 161 | finalize(timeout, #state{replies=Replies}=State) -> 162 | Merged = merge(Replies), 163 | ok = repair(Replies, State#state{groups = Merged}), 164 | case State#state.prune of 165 | true -> 166 | [riak_pg_vnode:prune(IndexNode) || {IndexNode, _Map} <- Replies]; 167 | false -> 168 | ok 169 | end, 170 | {stop, normal, State}. 171 | 172 | %%%=================================================================== 173 | %%% Internal Functions 174 | %%%=================================================================== 175 | 176 | %% @doc perform merge of replica CRDT maps 177 | merge([{_,H}|T]) -> 178 | merge(T,H). 179 | merge([], Merged) -> 180 | Merged; 181 | merge([{_,H}|T], Merged) -> 182 | merge(T, riak_dt_map:merge(H, Merged)). 183 | 184 | %% @doc Perform merge of replica pids. 185 | merge_pids(Replies, Group) -> 186 | Pids = lists:sort( 187 | proplists:get_value( 188 | {Group, riak_dt_orswot}, 189 | riak_dt_map:value(merge(Replies)), 190 | [] 191 | ) 192 | ), 193 | Pruned = lists:sort(riak_pg_util:prune(Pids)), 194 | {Pids == Pruned, Pruned}. 195 | 196 | %% @doc Trigger repair if necessary. 197 | repair([{IndexNode, Map}|Replies], State) -> 198 | case riak_dt_map:equal(Map, State#state.groups) of 199 | true -> 200 | ok; 201 | false -> 202 | riak_pg_vnode:repair(IndexNode, State#state.groups) 203 | end, 204 | repair(Replies, State); 205 | 206 | repair([], _State) -> 207 | ok. 208 | -------------------------------------------------------------------------------- /src/riak_pg_members_fsm_sup.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright (c) 2013 Christopher Meiklejohn. All Rights Reserved. 4 | %% 5 | %% This file is provided to you under the Apache License, 6 | %% Version 2.0 (the "License"); you may not use this file 7 | %% except in compliance with the License. You may obtain 8 | %% a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, 13 | %% software distributed under the License is distributed on an 14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | %% KIND, either express or implied. See the License for the 16 | %% specific language governing permissions and limitations 17 | %% under the License. 18 | %% 19 | %% ------------------------------------------------------------------- 20 | %% @author Christopher Meiklejohn 21 | %% @copyright 2013 Christopher Meiklejohn. 22 | %% @doc Supervisor for the membership requests. 23 | 24 | -module(riak_pg_members_fsm_sup). 25 | -author('Christopher Meiklejohn '). 26 | 27 | -behaviour(supervisor). 28 | 29 | %% API 30 | -export([start_link/0, 31 | start_child/1, 32 | terminate_child/2]). 33 | 34 | %% Supervisor callbacks 35 | -export([init/1]). 36 | 37 | %% =================================================================== 38 | %% API functions 39 | %% =================================================================== 40 | 41 | %% @doc API for starting the supervisor. 42 | start_link() -> 43 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 44 | 45 | %% @doc Start a child. 46 | start_child(Args) -> 47 | supervisor:start_child(?MODULE, Args). 48 | 49 | %% @doc Stop a child immediately 50 | terminate_child(Supervisor, Pid) -> 51 | supervisor:terminate_child(Supervisor, Pid). 52 | 53 | %% =================================================================== 54 | %% Supervisor callbacks 55 | %% =================================================================== 56 | 57 | %% @doc supervisor callback. 58 | init([]) -> 59 | Spec = {riak_pg_members_fsm, 60 | {riak_pg_members_fsm, start_link, []}, 61 | temporary, 5000, worker, [riak_pg_members_fsm]}, 62 | 63 | {ok, {{simple_one_for_one, 10, 10}, [Spec]}}. 64 | -------------------------------------------------------------------------------- /src/riak_pg_node_event_handler.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright (c) 2013 Christopher Meiklejohn. All Rights Reserved. 4 | %% 5 | %% This file is provided to you under the Apache License, 6 | %% Version 2.0 (the "License"); you may not use this file 7 | %% except in compliance with the License. You may obtain 8 | %% a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, 13 | %% software distributed under the License is distributed on an 14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | %% KIND, either express or implied. See the License for the 16 | %% specific language governing permissions and limitations 17 | %% under the License. 18 | %% 19 | %% ------------------------------------------------------------------- 20 | %% @author Christopher Meiklejohn 21 | %% @copyright 2013 Christopher Meiklejohn. 22 | %% @doc Node event handler. 23 | 24 | -module(riak_pg_node_event_handler). 25 | -author('Christopher Meiklejohn '). 26 | 27 | -behaviour(gen_event). 28 | 29 | %% gen_event callbacks 30 | -export([init/1, 31 | handle_event/2, 32 | handle_call/2, 33 | handle_info/2, 34 | terminate/2, 35 | code_change/3]). 36 | 37 | -record(state, {}). 38 | 39 | init([]) -> 40 | {ok, #state{}}. 41 | 42 | handle_event({service_update, _Services}, State) -> 43 | {ok, State}. 44 | 45 | handle_call(_Event, State) -> 46 | {ok, ok, State}. 47 | 48 | handle_info(_Info, State) -> 49 | {ok, State}. 50 | 51 | terminate(_Reason, _State) -> 52 | ok. 53 | 54 | code_change(_OldVsn, State, _Extra) -> 55 | {ok, State}. 56 | -------------------------------------------------------------------------------- /src/riak_pg_ring_event_handler.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright (c) 2013 Christopher Meiklejohn. All Rights Reserved. 4 | %% 5 | %% This file is provided to you under the Apache License, 6 | %% Version 2.0 (the "License"); you may not use this file 7 | %% except in compliance with the License. You may obtain 8 | %% a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, 13 | %% software distributed under the License is distributed on an 14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | %% KIND, either express or implied. See the License for the 16 | %% specific language governing permissions and limitations 17 | %% under the License. 18 | %% 19 | %% ------------------------------------------------------------------- 20 | %% @author Christopher Meiklejohn 21 | %% @copyright 2013 Christopher Meiklejohn. 22 | %% @doc Ring event handler. 23 | 24 | -module(riak_pg_ring_event_handler). 25 | -author('Christopher Meiklejohn '). 26 | 27 | -behaviour(gen_event). 28 | 29 | %% gen_event callbacks 30 | -export([init/1, 31 | handle_event/2, 32 | handle_call/2, 33 | handle_info/2, 34 | terminate/2, 35 | code_change/3]). 36 | 37 | -record(state, {}). 38 | 39 | init([]) -> 40 | {ok, #state{}}. 41 | 42 | handle_event({ring_update, _Ring}, State) -> 43 | {ok, State}. 44 | 45 | handle_call(_Event, State) -> 46 | {ok, ok, State}. 47 | 48 | handle_info(_Info, State) -> 49 | {ok, State}. 50 | 51 | terminate(_Reason, _State) -> 52 | ok. 53 | 54 | code_change(_OldVsn, State, _Extra) -> 55 | {ok, State}. 56 | -------------------------------------------------------------------------------- /src/riak_pg_sup.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright (c) 2013 Christopher Meiklejohn. All Rights Reserved. 4 | %% 5 | %% This file is provided to you under the Apache License, 6 | %% Version 2.0 (the "License"); you may not use this file 7 | %% except in compliance with the License. You may obtain 8 | %% a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, 13 | %% software distributed under the License is distributed on an 14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | %% KIND, either express or implied. See the License for the 16 | %% specific language governing permissions and limitations 17 | %% under the License. 18 | %% 19 | %% ------------------------------------------------------------------- 20 | %% @author Christopher Meiklejohn 21 | %% @copyright 2013 Christopher Meiklejohn. 22 | %% @doc Application supervisor. 23 | 24 | -module(riak_pg_sup). 25 | -author('Christopher Meiklejohn '). 26 | 27 | -behaviour(supervisor). 28 | 29 | %% API 30 | -export([start_link/0]). 31 | 32 | %% Supervisor callbacks 33 | -export([init/1]). 34 | 35 | %% =================================================================== 36 | %% API functions 37 | %% =================================================================== 38 | 39 | start_link() -> 40 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 41 | 42 | %% =================================================================== 43 | %% Supervisor callbacks 44 | %% =================================================================== 45 | 46 | init(_Args) -> 47 | VMaster = {riak_pg_vnode_master, 48 | {riak_core_vnode_master, start_link, [riak_pg_vnode]}, 49 | permanent, 5000, worker, [riak_core_vnode_master]}, 50 | 51 | CreateFSM = {riak_pg_create_fsm_sup, 52 | {riak_pg_create_fsm_sup, start_link, []}, 53 | permanent, infinity, supervisor, [riak_pg_create_fsm_sup]}, 54 | 55 | DeleteFSM = {riak_pg_delete_fsm_sup, 56 | {riak_pg_delete_fsm_sup, start_link, []}, 57 | permanent, infinity, supervisor, [riak_pg_delete_fsm_sup]}, 58 | 59 | JoinFSM = {riak_pg_join_fsm_sup, 60 | {riak_pg_join_fsm_sup, start_link, []}, 61 | permanent, infinity, supervisor, [riak_pg_join_fsm_sup]}, 62 | 63 | LeaveFSM = {riak_pg_leave_fsm_sup, 64 | {riak_pg_leave_fsm_sup, start_link, []}, 65 | permanent, infinity, supervisor, [riak_pg_leave_fsm_sup]}, 66 | 67 | MembersFSM = {riak_pg_members_fsm_sup, 68 | {riak_pg_members_fsm_sup, start_link, []}, 69 | permanent, infinity, supervisor, [riak_pg_members_fsm_sup]}, 70 | 71 | GroupsFsm = {riak_pg_groups_fsm_sup, 72 | { 73 | riak_pg_groups_fsm_sup, start_link, [] 74 | }, 75 | permanent, 76 | infinity, 77 | supervisor, 78 | [riak_pg_groups_fsm_sup] 79 | }, 80 | 81 | 82 | {ok, {{one_for_one, 5, 10}, [VMaster, 83 | CreateFSM, 84 | DeleteFSM, 85 | JoinFSM, 86 | LeaveFSM, 87 | GroupsFsm, 88 | MembersFSM]}}. 89 | -------------------------------------------------------------------------------- /src/riak_pg_util.erl: -------------------------------------------------------------------------------- 1 | -module(riak_pg_util). 2 | 3 | -export([prune/1]). 4 | 5 | %% @doc If the node is connected, and the process is not alive, prune 6 | %% it. 7 | pid_alive(Pid) when is_pid(Pid) -> 8 | lists:member(node(Pid), [node()| nodes()]) andalso is_process_alive(node(Pid), Pid). 9 | 10 | %% @doc Remote call to determine if process is alive or not; assume if 11 | %% the node fails communication it is, since we have no proof it 12 | %% is not. 13 | is_process_alive(Node, Pid) -> 14 | case rpc:call(Node, erlang, is_process_alive, [Pid]) of 15 | {badrpc, _} -> 16 | true; 17 | Value -> 18 | Value 19 | end. 20 | 21 | %% @doc Based on connected nodes, prune out processes that no longer 22 | %% exist. 23 | prune(List) -> 24 | lists:foldl( 25 | fun(Pid, Acc) -> 26 | case pid_alive(Pid) of 27 | true -> 28 | [Pid|Acc]; 29 | false -> 30 | Acc 31 | end 32 | end, 33 | [], 34 | List). 35 | -------------------------------------------------------------------------------- /src/riak_pg_vnode.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright (c) 2013 Christopher Meiklejohn, 2015 Mark Steele All Rights Reserved. 4 | %% 5 | %% This file is provided to you under the Apache License, 6 | %% Version 2.0 (the "License"); you may not use this file 7 | %% except in compliance with the License. You may obtain 8 | %% a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, 13 | %% software distributed under the License is distributed on an 14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | %% KIND, either express or implied. See the License for the 16 | %% specific language governing permissions and limitations 17 | %% under the License. 18 | %% 19 | %% ------------------------------------------------------------------- 20 | %% @author Christopher Meiklejohn 21 | %% @author Mark Steele 22 | %% @copyright 2013 Christopher Meiklejohn 23 | %% @copyright 2015 Mark Steele 24 | %% @doc pg vnode. 25 | 26 | -module(riak_pg_vnode). 27 | -author('Christopher Meiklejohn '). 28 | -author('Mark Steele '). 29 | 30 | -behaviour(riak_core_vnode). 31 | 32 | -include("riak_pg.hrl"). 33 | -include_lib("riak_core/include/riak_core_vnode.hrl"). 34 | 35 | -export([start_vnode/1, 36 | init/1, 37 | terminate/2, 38 | handle_command/3, 39 | is_empty/1, 40 | delete/1, 41 | handle_handoff_command/3, 42 | handoff_starting/2, 43 | handoff_cancelled/1, 44 | handoff_finished/2, 45 | handle_handoff_data/2, 46 | encode_handoff_item/2, 47 | handle_coverage/4, 48 | handle_exit/3]). 49 | 50 | -export([ 51 | delete/3, 52 | join/4, 53 | leave/4, 54 | groups/2, 55 | prune/1, 56 | repair/2, 57 | members/2]). 58 | 59 | 60 | -record(state, {node, partition, groups}). 61 | 62 | -define(MASTER,riak_pg_vnode_master). 63 | 64 | %% API 65 | start_vnode(I) -> 66 | riak_core_vnode_master:get_vnode_pid(I, ?MODULE). 67 | 68 | init([Partition]) -> 69 | {ok, #state{node=node(), partition=Partition, groups=riak_dt_map:new()}}. 70 | 71 | 72 | %% @doc Delete group. 73 | delete(Preflist, Identity, Group) -> 74 | riak_core_vnode_master:command(Preflist, 75 | {delete, Identity, Group}, 76 | {fsm, undefined, self()}, 77 | ?MASTER). 78 | 79 | %% @doc Join group. 80 | join(Preflist, Identity, Group, Pid) -> 81 | riak_core_vnode_master:command(Preflist, 82 | {join, Identity, Group, Pid}, 83 | {fsm, undefined, self()}, 84 | ?MASTER). 85 | 86 | %% @doc Leave group. 87 | leave(Preflist, Identity, Group, Pid) -> 88 | riak_core_vnode_master:command(Preflist, 89 | {leave, Identity, Group, Pid}, 90 | {fsm, undefined, self()}, 91 | ?MASTER). 92 | 93 | %% @doc Group members. 94 | members(Preflist, Identity) -> 95 | riak_core_vnode_master:command(Preflist, 96 | {members, Identity}, 97 | {fsm, undefined, self()}, 98 | ?MASTER). 99 | 100 | %% @doc Perform repair. 101 | repair(IndexNode, Map) -> 102 | riak_core_vnode_master:command(IndexNode, 103 | {repair, Map}, 104 | ignore, 105 | ?MASTER). 106 | 107 | %% @doc Trigger pruning 108 | prune(IndexNode) -> 109 | riak_core_vnode_master:command(IndexNode, 110 | prune, 111 | ignore, 112 | ?MASTER). 113 | 114 | groups(Preflist, ReqId) -> 115 | riak_core_vnode_master:coverage({groups, ReqId}, 116 | Preflist, 117 | all, 118 | {fsm, undefined, self()}, 119 | ?MASTER). 120 | 121 | %% @doc Perform join as part of repair. 122 | handle_command({repair, Map}, 123 | _Sender, 124 | #state{groups=Groups0, partition=Partition}=State) -> 125 | NewGroups = riak_dt_map:merge(Groups0, Map), 126 | NewPruned = prune_groups(NewGroups, Partition), 127 | {noreply, State#state{groups=NewPruned}}; 128 | 129 | handle_command(prune, 130 | _Sender, 131 | #state{groups=Groups0, partition=Partition}=State) -> 132 | Pruned = prune_groups(Groups0, Partition), 133 | {noreply, State#state{groups=Pruned}}; 134 | 135 | %% @doc Respond to a members request. 136 | handle_command({members, {ReqId, _}}, 137 | _Sender, 138 | #state{groups=Groups, partition=Partition, node=Node}=State) -> 139 | {reply, {ok, ReqId, {Partition, Node}, Groups}, State}; 140 | 141 | %% @doc Respond to a delete request. 142 | handle_command({delete, {ReqId, _}, Group}, 143 | _Sender, 144 | #state{groups=Groups0, partition=Partition}=State) -> 145 | Groups = case riak_dt_map:update( 146 | {update, [{remove, {Group, riak_dt_orswot}}]}, 147 | Partition, 148 | Groups0 149 | ) of 150 | {ok, Groups1} -> 151 | Groups1; 152 | _ -> 153 | Groups0 154 | end, 155 | {reply, {ok, ReqId}, State#state{groups=Groups}}; 156 | 157 | %% @doc Respond to a join request. 158 | handle_command({join, {ReqId, _}, Group, Pid}, 159 | _Sender, 160 | #state{groups=Groups0, partition=Partition}=State) -> 161 | {ok, Groups} = riak_dt_map:update( 162 | {update, [{update, {Group, riak_dt_orswot}, {add, Pid}}]}, 163 | Partition, 164 | Groups0 165 | ), 166 | {reply, {ok, ReqId}, State#state{groups=Groups}}; 167 | 168 | %% @doc Respond to a leave request. 169 | handle_command({leave, {ReqId, _}, Group, Pid}, 170 | _Sender, 171 | #state{groups=Groups0, partition=Partition}=State) -> 172 | NewGroups = case riak_dt_map:update( 173 | {update, [{update, {Group, riak_dt_orswot}, {remove, Pid}}]}, 174 | Partition, 175 | Groups0 176 | ) of 177 | {ok, Groups} -> 178 | Groups; 179 | _ -> 180 | Groups0 181 | end, 182 | {reply, {ok, ReqId}, State#state{groups=NewGroups}}; 183 | 184 | 185 | %% @doc Default handler. 186 | handle_command(Message, _Sender, State) -> 187 | ?PRINT({unhandled_command, Message}), 188 | {noreply, State}. 189 | 190 | %% @doc Fold over the dict for handoff. 191 | handle_handoff_command(?FOLD_REQ{foldfun=Fun, acc0=Acc0}, _Sender, State) -> 192 | Acc = lists:foldl(Fun, Acc0, [{foo,State#state.groups}]), 193 | {reply, Acc, State}. 194 | 195 | handoff_starting(_TargetNode, State) -> 196 | {true, State}. 197 | 198 | handoff_cancelled(State) -> 199 | {ok, State}. 200 | 201 | handoff_finished(_TargetNode, State) -> 202 | {ok, State}. 203 | 204 | %% @doc Handle receiving data from handoff. 205 | handle_handoff_data(Data, #state{groups=Groups0}=State) -> 206 | CRDT = riak_dt:from_binary(Data), 207 | Groups = riak_dt_map:merge(Groups0, CRDT), 208 | {reply, ok, State#state{groups=Groups}}. 209 | 210 | encode_handoff_item(_, Data) -> 211 | riak_dt:to_binary(Data). 212 | 213 | is_empty(#state{groups=Groups}=State) -> 214 | case length(riak_dt_map:value(Groups)) of 215 | 0 -> 216 | {true, State}; 217 | _ -> 218 | {false, State} 219 | end. 220 | 221 | delete(State) -> 222 | {ok, State}. 223 | 224 | handle_coverage(groups, _KeySpaces, _Sender, State=#state{groups=Groups,partition=Partition}) -> 225 | NewGroups = prune_groups(Groups,Partition), 226 | GroupList = [Group || {{Group, riak_dt_orswot}, _Pids} 227 | <- riak_dt_map:value(NewGroups)], 228 | {reply, GroupList, State#state{groups=NewGroups}}; 229 | 230 | handle_coverage(_Req, _KeySpaces, _Sender, State) -> 231 | {stop, not_implemented, State}. 232 | 233 | handle_exit(_Pid, _Reason, State) -> 234 | {noreply, State}. 235 | 236 | terminate(_Reason, _State) -> 237 | ok. 238 | 239 | %%%=================================================================== 240 | %%% Internal Functions 241 | %%%=================================================================== 242 | 243 | prune_groups(CRDT, Partition) -> 244 | prune_groups(CRDT, Partition, riak_dt_map:value(CRDT)). 245 | 246 | prune_groups(CRDT0, Partition, [{{Group, riak_dt_orswot},Pids}|List]) 247 | when length(Pids) =/= 0-> 248 | Pruned = riak_pg_util:prune(Pids), 249 | case length(Pruned) =:= length(Pids) of 250 | true -> 251 | prune_groups(CRDT0, Partition, List); 252 | false -> 253 | case length(Pruned) =/= 0 of 254 | true -> 255 | Remove = lists:filter( 256 | fun(X) -> 257 | not lists:member(X, Pruned) 258 | end, 259 | Pids), 260 | {ok, CRDT} = riak_dt_map:update( 261 | {update, [{update, 262 | {Group, riak_dt_orswot}, 263 | {remove,Remove} 264 | }]}, 265 | Partition, 266 | CRDT0 267 | ); 268 | false -> 269 | {ok, CRDT} = riak_dt_map:update( 270 | {update, [{remove, {Group, riak_dt_orswot}}]}, 271 | Partition, 272 | CRDT0 273 | ) 274 | end, 275 | prune_groups(CRDT, Partition, List) 276 | end; 277 | 278 | prune_groups(CRDT0, Partition, [{{Group, riak_dt_orswot},_Pids}|List]) -> 279 | {ok, CRDT} = riak_dt_map:update( 280 | {update, [{remove, {Group, riak_dt_orswot}}]}, 281 | Partition, 282 | CRDT0 283 | ), 284 | prune_groups(CRDT, Partition, List); 285 | prune_groups(CRDT, _Partition, []) -> 286 | CRDT. 287 | -------------------------------------------------------------------------------- /tools.mk: -------------------------------------------------------------------------------- 1 | REBAR ?= ./rebar 2 | 3 | test: compile 4 | ${REBAR} eunit skip_deps=true 5 | 6 | docs: 7 | ${REBAR} doc skip_deps=true 8 | 9 | xref: compile 10 | ${REBAR} xref skip_deps=true 11 | 12 | PLT ?= $(HOME)/.combo_dialyzer_plt 13 | LOCAL_PLT = .local_dialyzer_plt 14 | DIALYZER_FLAGS ?= -Wunmatched_returns -Werror_handling -Wrace_conditions -Wunderspecs 15 | 16 | ${PLT}: compile 17 | @if [ -f $(PLT) ]; then \ 18 | dialyzer --check_plt --plt $(PLT) --apps $(DIALYZER_APPS) && \ 19 | dialyzer --add_to_plt --plt $(PLT) --output_plt $(PLT) --apps $(DIALYZER_APPS) ; test $$? -ne 1; \ 20 | else \ 21 | dialyzer --build_plt --output_plt $(PLT) --apps $(DIALYZER_APPS); test $$? -ne 1; \ 22 | fi 23 | 24 | ${LOCAL_PLT}: compile 25 | @if [ -d deps ]; then \ 26 | if [ -f $(LOCAL_PLT) ]; then \ 27 | dialyzer --check_plt --plt $(LOCAL_PLT) deps/*/ebin && \ 28 | dialyzer --add_to_plt --plt $(LOCAL_PLT) --output_plt $(LOCAL_PLT) deps/*/ebin ; test $$? -ne 1; \ 29 | else \ 30 | dialyzer --build_plt --output_plt $(LOCAL_PLT) deps/*/ebin ; test $$? -ne 1; \ 31 | fi \ 32 | fi 33 | 34 | dialyzer: ${PLT} ${LOCAL_PLT} 35 | @echo "==> $(shell basename $(shell pwd)) (dialyzer)" 36 | @if [ -f $(LOCAL_PLT) ]; then \ 37 | PLTS="$(PLT) $(LOCAL_PLT)"; \ 38 | else \ 39 | PLTS=$(PLT); \ 40 | fi; \ 41 | if [ -f dialyzer.ignore-warnings ]; then \ 42 | if [ $$(grep -cvE '[^[:space:]]' dialyzer.ignore-warnings) -ne 0 ]; then \ 43 | echo "ERROR: dialyzer.ignore-warnings contains a blank/empty line, this will match all messages!"; \ 44 | exit 1; \ 45 | fi; \ 46 | dialyzer $(DIALYZER_FLAGS) --plts $${PLTS} -c ebin > dialyzer_warnings ; \ 47 | egrep -v "^[[:space:]]*(done|Checking|Proceeding|Compiling)" dialyzer_warnings | grep -F -f dialyzer.ignore-warnings -v > dialyzer_unhandled_warnings ; \ 48 | cat dialyzer_unhandled_warnings ; \ 49 | [ $$(cat dialyzer_unhandled_warnings | wc -l) -eq 0 ] ; \ 50 | else \ 51 | dialyzer $(DIALYZER_FLAGS) --plts $${PLTS} -c ebin; \ 52 | fi 53 | 54 | cleanplt: 55 | @echo 56 | @echo "Are you sure? It takes several minutes to re-build." 57 | @echo Deleting $(PLT) and $(LOCAL_PLT) in 5 seconds. 58 | @echo 59 | sleep 5 60 | rm $(PLT) 61 | rm $(LOCAL_PLT) 62 | 63 | --------------------------------------------------------------------------------