├── .gitignore ├── .hgignore ├── .hgtags ├── AUTHORS ├── Emakefile ├── LICENSE ├── Makefile ├── README.md ├── Rakefile ├── doc └── overview.edoc ├── ebin ├── erldis.app └── erldis.appup ├── include └── erldis.hrl ├── rebar ├── src ├── erldis.erl ├── erldis_app.erl ├── erldis_binaries.erl ├── erldis_client.erl ├── erldis_dict.erl ├── erldis_list.erl ├── erldis_proto.erl ├── erldis_sets.erl ├── erldis_sup.erl ├── erldis_sync_client.erl └── gen_server2.erl └── test ├── a_erldis_multiexec_tests.erl ├── erldis_dict_tests.erl ├── erldis_list_compatibility_tests.erl ├── erldis_list_tests.erl ├── erldis_multiple.erl ├── erldis_pipelining_tests.erl ├── erldis_pubsub_tests.erl ├── erldis_sets_tests.erl ├── erldis_tests.erl └── proto_tests.erl /.gitignore: -------------------------------------------------------------------------------- 1 | .hg/ 2 | .hgignore 3 | *.beam 4 | .plt 5 | -------------------------------------------------------------------------------- /.hgignore: -------------------------------------------------------------------------------- 1 | syntax: glob 2 | 3 | *.beam 4 | .plt 5 | .git 6 | *~ 7 | -------------------------------------------------------------------------------- /.hgtags: -------------------------------------------------------------------------------- 1 | 03d4a77352541bf71109c22f1a8eb5d1ebacb569 0.3.0 2 | d2417eb226ef7251b3526a6188554899e70a3f0d 0.3.1 3 | 932f30ad5d9f9cae0215c7e2945a786b17e4239f 0.3.2 4 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Valentino Volonghi 2 | - initial codebase 3 | http://bitbucket.org/adroll/erldis/ 4 | 5 | Jacob Perkins 6 | - synchronous client 7 | - stdlib emulation modules 8 | http://bitbucket.org/japerk/erldis/ 9 | 10 | Eric Cestari 11 | - binary communication 12 | - MULTI/EXEC 13 | http://github.com/cstar/erldis 14 | 15 | tonyg 16 | - BLPOP & BRPOP 17 | https://bitbucket.org/tonyg/erldis/ -------------------------------------------------------------------------------- /Emakefile: -------------------------------------------------------------------------------- 1 | {"src/*", [ 2 | debug_info, 3 | {i, "include/"}, 4 | {outdir, "ebin/"} 5 | ]}. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 2 | adroll.com 3 | Valentino Volonghi 4 | 5 | Copyright (c) 2009, 2010 6 | weotta.com 7 | Jacob Perkins 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining 10 | a copy of this software and associated documentation files (the 11 | "Software"), to deal in the Software without restriction, including 12 | without limitation the rights to use, copy, modify, merge, publish, 13 | distribute, sublicense, and/or sell copies of the Software, and to 14 | permit persons to whom the Software is furnished to do so, subject to 15 | the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be 18 | included in all copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ERL=erl 2 | # store output so is only executed once 3 | ERL_LIBS=$(shell erl -eval 'io:format("~s~n", [code:lib_dir()])' -s init stop -noshell) 4 | # get application vsn from app file 5 | VSN=$(shell erl -pa ebin/ -eval 'application:load(erldis), {ok, Vsn} = application:get_key(erldis, vsn), io:format("~s~n", [Vsn])' -s init stop -noshell) 6 | 7 | all: src 8 | 9 | src: FORCE 10 | @$(ERL) -make 11 | 12 | clean: 13 | rm -f erl_crash.dump *.beam */*.beam 14 | 15 | TEST_SOURCES := $(wildcard test/*.erl) 16 | TEST_MODULES = $(TEST_SOURCES:test/%.erl=%) 17 | 18 | test: src FORCE $(TEST_MODULES) 19 | 20 | ./$(TEST_MODULES): 21 | @echo "Running tests for $@" 22 | erlc -pa ebin/ -o ebin/ -I include/ test/$@.erl 23 | erl -pa ebin/ -run $@ test -run init stop -noshell 24 | 25 | install: all 26 | mkdir -p $(ERL_LIBS)/erldis-$(VSN)/ebin 27 | mkdir -p $(ERL_LIBS)/erldis-$(VSN)/include 28 | for i in ebin/*.beam; do install $$i $(ERL_LIBS)/erldis-$(VSN)/$$i ; done 29 | for i in include/*.hrl; do install $$i $(ERL_LIBS)/erldis-$(VSN)/$$i ; done 30 | # also install .app file 31 | install ebin/erldis.app $(ERL_LIBS)/erldis-$(VSN)/ebin/erldis.app 32 | install ebin/erldis.appup $(ERL_LIBS)/erldis-$(VSN)/ebin/erldis.appup 33 | 34 | plt: 35 | @dialyzer --build_plt --output_plt .plt -q -r . -I include/ 36 | 37 | check: all 38 | @dialyzer --check_plt --plt .plt -q -r . -I include/ 39 | 40 | FORCE: -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | erldis : erlang client library for redis 2 | ---------------------------------------- 3 | 4 | This is a fork from over at [bitbucket](http://bitbucket.org/japerk/erldis). 5 | 6 | Code written by [Valentino Volonghi](http://bitbucket.org/dialtone/) and [Jacob Perkins](http://bitbucket.org/japerk/) -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | task :default => :build 2 | 3 | if File.file?('erlang_config.rb') 4 | require 'erlang_config' 5 | else 6 | puts "erlang_config.rb file is missing." 7 | puts "You need to fill it with your local configuration." 8 | puts "An sample has been generated for you." 9 | File.open("erlang_config.rb",'w') do |file| 10 | file.write("ERL_TOP=\"\"\n") 11 | file.write("EMAKE_COMPILE_OPTIONS = []\n") 12 | end 13 | exit(-1) 14 | end 15 | 16 | task :build do 17 | sh "#{ERL_TOP}/bin/erl -make" 18 | end 19 | 20 | desc "installs in $ERL_TOP/lib/" 21 | task :install => [:build] do |t| 22 | FileList.new('ebin/*.app').each do |dir| 23 | #vsn = extract_version_information("vsn.config","vsn").gsub("\"","") 24 | name = dir.gsub("ebin/","").gsub(".app","") 25 | destination = "#{erlang_home}/lib/#{name}" 26 | puts "#{name} will be installed in #{destination}" 27 | sh "mkdir -p #{destination}" 28 | %w{ebin doc include }.each do |d| 29 | sh "cp -R #{d} #{destination}" 30 | end 31 | end 32 | end 33 | 34 | 35 | def erlang_home 36 | @erlang_home||=IO.popen("#{ERL_TOP}/bin/erl -noinput -noshell -eval 'io:format(code:root_dir()).' -s init stop").readlines[0] 37 | end 38 | def extract_version_information(file, type) 39 | informations = [] 40 | IO.foreach(file) { |line| 41 | informations << $1 if line =~ /\{#{type},(.*)\}/ 42 | } 43 | informations[0] 44 | end 45 | -------------------------------------------------------------------------------- /doc/overview.edoc: -------------------------------------------------------------------------------- 1 | @doc Redis erlang client 2 | 3 | From dialtone on erldis_client: 4 | handle_info is the function that deals with parsing every line that comes from redis. save_or_reply is what is currently used to send replies back to the users. 5 | going through the State parameters: 6 | * socket is of course the current connection to redis 7 | * buffer contains intermediate results of parsing. it's used for multi-bulk replies. but it gets handy also for single bulk replies, although clearly it will be at most long 1 in those cases 8 | * reply_caller is a function used to abstract the reply to the user. since the call is synchronous, the client might put itself in listening slower than redis can answer the request. when it manages to listen before the answer it adds a call that registers the call request and when it's done the system uses that function to send back the results. if it comes afterwards it simply gets the results in save_or_reply the function checks for reply_caller and if it's not there it just appends to results 9 | * the field remaining is for how many remaining packets you need to handle before the end of this call. this is also especially useful for multi-bulk replies. when a call is made it's set to 1 then the rest of the state machine figures out if only 1 packet will be received or how many more (the state machine is in erldis_proto) 10 | * then the field calls is the number of calls that are waiting. basically at the end of a call remaining would go to 0. but if it goes to 0 the state machine would parse the remaining stuff. so using calls we keep track of how many more calls are incoming and until calls is 0 remaining is reset to 1 when it goes to 0 at the end of a parsing. and this is done in save_or_reply 11 | * then pstate is the parser state, there can be only 4 right now error, hold, read, empty. error means that the next line is an error message. hold means that the next number is the number of multi-bulk items in the reply and is used to set the remaining field to something else than 1. read tells you how long the next field is so you need to read those bytes + 2 (\r\n are added by redis but not counted in the bytes... I fought hard to avoid this but salvatore didn't change it...). then empty means that the system is ready to accept a new reply. the function trim2 exists just to remove the \r\n at the end of the reply 12 | -------------------------------------------------------------------------------- /ebin/erldis.app: -------------------------------------------------------------------------------- 1 | {application, erldis, [ 2 | {description, "Erlang Redis application"}, 3 | {vsn, "0.3.2"}, 4 | {registered, [erldis_sup]}, 5 | {mod, {erldis_app, []}}, 6 | {applications, [kernel, stdlib]}, 7 | {modules, [ 8 | erldis_client, erldis, erldis_proto, erldis_app, erldis_sup, 9 | erldis_sets, erldis_dict, erldis_list, gen_server2, erldis_sync_client, 10 | erldis_binaries 11 | ]}, 12 | {env, [{host, "localhost"}, {port, 6379}, {timeout, 500}]} 13 | ]}. 14 | -------------------------------------------------------------------------------- /ebin/erldis.appup: -------------------------------------------------------------------------------- 1 | {"0.3.2", [ 2 | {"0.3.1", [{load_module, erldis}]}, 3 | {"0.3.0", [ 4 | {load_module, erldis_proto}, 5 | {load_module, erldis_client} 6 | ]}, 7 | {"0.2.1", [{restart_application, erldis}]}, 8 | {"0.2.0", [{load_module, erldis}]}, 9 | {"0.1.6", [ 10 | {add_module, erldis_binaries}, 11 | {load_module, erldis_client}, 12 | {load_module, erldis} 13 | ]}, 14 | {"0.1.5", [{load_module, erldis}]}, 15 | {"0.1.4", [{load_module, erldis_client}]}, 16 | {"0.1.3", [{load_module, erldis_client}]}, 17 | {"0.1.2", [{load_module, erldis_client}]}, 18 | {"0.1.1", [ 19 | {load_module, erldis}, 20 | {load_module, erldis_client}, 21 | {load_module, erldis_dict}, 22 | {load_module, erldis_list}, 23 | {load_module, erldis_sets} 24 | ]}, 25 | {"0.1.0", [ 26 | {load_module, erldis_client}, 27 | {load_module, erldis} 28 | ]}, 29 | {"0.0.12", [{restart_application, erldis}]}, 30 | {"0.0.11", [ 31 | {load_module, erldis}, 32 | {load_module, erldis_proto}, 33 | {load_module, erldis_sync_client} 34 | ]}, 35 | {"0.0.10", [ 36 | {load_module, erldis_client}, 37 | {load_module, erldis_client} 38 | ]}, 39 | {"0.0.9", [{load_module, erldis_client}]}, 40 | {"0.0.8", [{load_module, erldis_client}]}, 41 | {"0.0.7", [{load_module, erldis_client}]}, 42 | {"0.0.6", [ 43 | {load_module, erldis_client}, 44 | {load_module, erldis_dict}, 45 | {load_module, erldis_list} 46 | ]}, 47 | {"0.0.5", [ 48 | {load_module, erldis_client}, 49 | {load_module, erldis_sets}, 50 | {add_module, erldis_dict}, 51 | {add_module, erldis_list} 52 | ]}, 53 | {"0.0.4", [ 54 | {load_module, erldis_client}, 55 | {load_module, erldis_sets} 56 | ]}, 57 | {"0.0.3", [ 58 | {load_module, erldis_client}, 59 | {load_module, erldis_sets} 60 | ]}, 61 | {"0.0.2", [ 62 | {add_module, erldis_client}, 63 | {load_module, erldis_sets} 64 | ]}, 65 | {"0.0.1", [ 66 | {add_module, erldis_client}, 67 | {add_module, erldis_proto}, 68 | {load_module, erldis}, 69 | {delete_module, client}, 70 | {delete_module, proto}, 71 | {add_module, erldis_sets} 72 | ]} 73 | ], [ 74 | {"0.3.1", [{load_module, erldis}]}, 75 | {"0.3.0", [ 76 | {load_module, erldis_proto}, 77 | {load_module, erldis_client} 78 | ]}, 79 | {"0.2.1", [{restart_application, erldis}]}, 80 | {"0.2.0", [{load_module, erldis}]}, 81 | {"0.1.6", [ 82 | {load_module, erldis_client}, 83 | {load_module, erldis}, 84 | {delete_module, erldis_binaries} 85 | ]}, 86 | {"0.1.5", [{load_module, erldis}]}, 87 | {"0.1.4", [{load_module, erldis_client}]}, 88 | {"0.1.3", [{load_module, erldis_client}]}, 89 | {"0.1.2", [{load_module, erldis_client}]}, 90 | {"0.1.1", [ 91 | {load_module, erldis}, 92 | {load_module, erldis_client}, 93 | {load_module, erldis_dict}, 94 | {load_module, erldis_list}, 95 | {load_module, erldis_sets} 96 | ]}, 97 | {"0.1.0", [ 98 | {load_module, erldis_client}, 99 | {load_module, erldis} 100 | ]}, 101 | {"0.0.12", [{restart_application, erldis}]}, 102 | {"0.0.11", [ 103 | {load_module, erldis}, 104 | {load_module, erldis_proto}, 105 | {load_module, erldis_sync_client} 106 | ]}, 107 | {"0.0.10", [ 108 | {load_module, erldis_client}, 109 | {load_module, erldis_client} 110 | ]}, 111 | {"0.0.9", [{load_module, erldis_client}]}, 112 | {"0.0.8", [{load_module, erldis_client}]}, 113 | {"0.0.7", [{load_module, erldis_client}]}, 114 | {"0.0.6", [ 115 | {load_module, erldis_client}, 116 | {load_module, erldis_dict}, 117 | {load_module, erldis_list} 118 | ]}, 119 | {"0.0.5", [ 120 | {load_module, erldis_client}, 121 | {load_module, erldis_sets}, 122 | {delete_module, erldis_dict}, 123 | {delete_module, erldis_list} 124 | ]}, 125 | {"0.0.4", [ 126 | {load_module, erldis_client}, 127 | {load_module, erldis_sets} 128 | ]}, 129 | {"0.0.3", [ 130 | {load_module, erldis_client}, 131 | {load_module, erldis_sets} 132 | ]}, 133 | {"0.0.2", [ 134 | {delete_module, erldis_client}, 135 | {load_module, erldis_sets} 136 | ]}, 137 | {"0.0.1", [ 138 | {add_module, client}, 139 | {add_module, proto}, 140 | {load_module, erldis}, 141 | {delete_module, erldis_client}, 142 | {delete_module, erldis_proto}, 143 | {delete_module, erldis_sets} 144 | ]} 145 | ]}. 146 | -------------------------------------------------------------------------------- /include/erldis.hrl: -------------------------------------------------------------------------------- 1 | -record(redis, { 2 | socket, buffer=[], reply_caller, pipeline=false, calls=0, remaining=0, 3 | pstate=empty, results=[], host, port, timeout, db = <<"0">>, subscribers 4 | }). 5 | 6 | -define(EOL, "\r\n"). 7 | -------------------------------------------------------------------------------- /rebar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/japerk/erldis/f48fa0662f2c7bdae6da96190df4a3d4756bbb3f/rebar -------------------------------------------------------------------------------- /src/erldis.erl: -------------------------------------------------------------------------------- 1 | -module(erldis). 2 | 3 | -include("erldis.hrl"). 4 | 5 | -compile(export_all). 6 | 7 | %%%%%%%%%%%%%%%%%%%%%%% 8 | %% Client Connection %% 9 | %%%%%%%%%%%%%%%%%%%%%%% 10 | 11 | connect() -> erldis_client:connect(). 12 | 13 | connect(Host) -> erldis_client:connect(Host). 14 | 15 | connect(Host, Port) -> erldis_client:connect(Host, Port). 16 | 17 | connect(Host, Port, Options) -> erldis_client:connect(Host, Port, Options). 18 | 19 | quit(Client) -> erldis_client:stop(Client). 20 | 21 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 22 | %% Commands operating on every value %% 23 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 24 | 25 | exists(Client, Key) -> erldis_client:sr_scall(Client, [<<"exists">>, Key]). 26 | 27 | del(Client, Key) -> erldis_client:sr_scall(Client, [<<"del">>, Key]). 28 | 29 | type(Client, Key) -> erldis_client:sr_scall(Client, [<<"type">>, Key]). 30 | 31 | keys(Client, Pattern) -> 32 | case erldis_client:scall(Client, [<<"keys">>, Pattern]) of 33 | [] -> []; 34 | B -> B 35 | end. 36 | 37 | % TODO: test randomkey, rename, renamenx, dbsize, expire, ttl 38 | 39 | randomkey(Client, Key) -> 40 | erldis_client:sr_scall(Client, [<<"randomkey">>, Key]). 41 | 42 | rename(Client, OldKey, NewKey) -> 43 | erldis_client:sr_scall(Client, [<<"rename">>, OldKey, NewKey]). 44 | 45 | renamenx(Client, OldKey, NewKey) -> 46 | erldis_client:sr_scall(Client, [<<"renamenx">>, OldKey, NewKey]). 47 | 48 | dbsize(Client) -> numeric(erldis_client:sr_scall(Client, [<<"dbsize">>])). 49 | 50 | expire(Client, Key, Seconds) -> 51 | erldis_client:sr_scall(Client, [<<"expire">>, Key, Seconds]). 52 | 53 | ttl(Client, Key) -> erldis_client:sr_scall(Client, [<<"ttl">>, Key]). 54 | 55 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 56 | %% Commands operating on string values %% 57 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 58 | 59 | set(Client, Key, Value) -> 60 | erldis_client:sr_scall(Client, [<<"set">>, Key, Value]). 61 | 62 | get(Client, Key) -> erldis_client:sr_scall(Client, [<<"get">>, Key]). 63 | 64 | getset(Client, Key, Value) -> 65 | erldis_client:sr_scall(Client, [<<"getset">>, Key, Value]). 66 | 67 | mget(Client, Keys) -> erldis_client:scall(Client, [<<"mget">> | Keys]). 68 | 69 | setnx(Client, Key, Value) -> 70 | erldis_client:sr_scall(Client, [<<"setnx">>, Key, Value]). 71 | 72 | %% TODO: setex, mset, msetnx 73 | 74 | incr(Client, Key) -> 75 | numeric(erldis_client:sr_scall(Client, [<<"incr">>, Key])). 76 | 77 | incrby(Client, Key, By) -> 78 | numeric(erldis_client:sr_scall(Client, [<<"incrby">>, Key, By])). 79 | 80 | decr(Client, Key) -> 81 | numeric(erldis_client:sr_scall(Client, [<<"decr">>, Key])). 82 | 83 | decrby(Client, Key, By) -> 84 | numeric(erldis_client:sr_scall(Client, [<<"decrby">>, Key, By])). 85 | 86 | %% TODO: append, substr 87 | 88 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 89 | %% Commands operating on lists %% 90 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 91 | 92 | rpush(Client, Key, Value) -> 93 | numeric(erldis_client:sr_scall(Client, [<<"rpush">>, Key, Value])). 94 | 95 | lpush(Client, Key, Value) -> 96 | numeric(erldis_client:sr_scall(Client, [<<"lpush">>, Key, Value])). 97 | 98 | llen(Client, Key) -> 99 | numeric(erldis_client:sr_scall(Client, [<<"llen">>, Key])). 100 | 101 | lrange(Client, Key, Start, End) -> 102 | erldis_client:scall(Client, [<<"lrange">>, Key, Start, End]). 103 | 104 | ltrim(Client, Key, Start, End) -> 105 | erldis_client:sr_scall(Client, [<<"ltrim">>, Key, Start, End]). 106 | 107 | lindex(Client, Key, Index) -> 108 | erldis_client:sr_scall(Client, [<<"lindex">>, Key, Index]). 109 | 110 | lset(Client, Key, Index, Value) -> 111 | erldis_client:sr_scall(Client, [<<"lset">>, Key, Index, Value]). 112 | 113 | lrem(Client, Key, Number, Value) -> 114 | numeric(erldis_client:sr_scall(Client, [<<"lrem">>, Key, Number, Value])). 115 | 116 | lpop(Client, Key) -> erldis_client:sr_scall(Client, [<<"lpop">>, Key]). 117 | 118 | rpop(Client, Key) -> erldis_client:sr_scall(Client, [<<"rpop">>, Key]). 119 | 120 | blpop(Client, Keys) -> blpop(Client, Keys, infinity). 121 | 122 | blpop(Client, Keys, Timeout) -> erldis_client:bcall(Client, [<<"blpop">> | Keys], Timeout). 123 | 124 | brpop(Client, Keys) -> brpop(Client, Keys, infinity). 125 | 126 | brpop(Client, Keys, Timeout) -> erldis_client:bcall(Client, [<<"brpop">> | Keys], Timeout). 127 | 128 | % TODO: rpoplpush 129 | 130 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 131 | %% Commands operating on sets %% 132 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 133 | 134 | sadd(Client, Key, Member) -> 135 | erldis_client:sr_scall(Client, [<<"sadd">>, Key, Member]). 136 | 137 | srem(Client, Key, Member) -> 138 | erldis_client:sr_scall(Client, [<<"srem">>, Key, Member]). 139 | 140 | spop(Client, Key) -> 141 | erldis_client:sr_scall(Client, [<<"spop">>, Key]). 142 | 143 | % TODO: test 144 | smove(Client, SrcKey, DstKey, Member) -> 145 | erldis_client:sr_scall(Client, [<<"smove">>, SrcKey, DstKey, Member]). 146 | 147 | scard(Client, Key) -> 148 | numeric(erldis_client:sr_scall(Client, [<<"scard">>, Key])). 149 | 150 | sismember(Client, Key, Member) -> 151 | erldis_client:sr_scall(Client, [<<"sismember">>, Key, Member]). 152 | 153 | sintersect(Client, Keys) -> sinter(Client, Keys). 154 | 155 | sinter(Client, Keys) -> erldis_client:scall(Client, [<<"sinter">> | Keys]). 156 | 157 | sinterstore(Client, DstKey, Keys) -> 158 | numeric(erldis_client:sr_scall(Client, [<<"sinterstore">>, DstKey | Keys])). 159 | 160 | sunion(Client, Keys) -> erldis_client:scall(Client, [<<"sunion">> | Keys]). 161 | 162 | sunionstore(Client, DstKey, Keys) -> 163 | numeric(erldis_client:sr_scall(Client, [<<"sunionstore">>, DstKey | Keys])). 164 | 165 | sdiff(Client, Keys) -> erldis_client:scall(Client, [<<"sdiff">> | Keys]). 166 | 167 | sdiffstore(Client, DstKey, Keys) -> 168 | numeric(erldis_client:sr_scall(Client, [<<"sdiffstore">>, DstKey | Keys])). 169 | 170 | smembers(Client, Key) -> erldis_client:scall(Client, [<<"smembers">>, Key]). 171 | 172 | %% TODO: srandmember 173 | 174 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 175 | %% Commands operating on ordered sets %% 176 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 177 | 178 | zadd(Client, Key, Score, Member) -> 179 | erldis_client:sr_scall(Client, [<<"zadd">>, Key, Score, Member]). 180 | 181 | zrem(Client, Key, Member) -> 182 | erldis_client:sr_scall(Client, [<<"zrem">>, Key, Member]). 183 | 184 | zincrby(Client, Key, By, Member) -> 185 | numeric(erldis_client:sr_scall(Client, [<<"zincrby">>, Key, By, Member])). 186 | 187 | %% TODO: zrank, zrevrank 188 | 189 | zrange(Client, Key, Start, End) -> 190 | erldis_client:scall(Client, [<<"zrange">>, Key, Start, End]). 191 | 192 | zrange_withscores(Client, Key, Start, End) -> 193 | Args = [<<"zrange">>, Key, Start, End, <<"withscores">>], 194 | withscores(erldis_client:scall(Client, Args)). 195 | 196 | zrevrange(Client, Key, Start, End) -> 197 | erldis_client:scall(Client, [<<"zrevrange">>, Key, Start, End]). 198 | 199 | zrevrange_withscores(Client, Key, Start, End) -> 200 | Args = [<<"zrevrange">>, Key, Start, End, <<"withscores">>], 201 | withscores(erldis_client:scall(Client, Args)). 202 | 203 | zrangebyscore(Client, Key, Min, Max) -> 204 | erldis_client:scall(Client, [<<"zrangebyscore">>, Key, Min, Max]). 205 | 206 | zrangebyscore(Client, Key, Min, Max, Offset, Count) -> 207 | Cmd = [<<"zrangebyscore">>, Key, Min, Max, <<"limit">>, Offset, Count], 208 | erldis_client:scall(Client, Cmd). 209 | 210 | zcard(Client, Key) -> 211 | numeric(erldis_client:sr_scall(Client, [<<"zcard">>, Key])). 212 | 213 | zscore(Client, Key, Member) -> 214 | numeric(erldis_client:sr_scall(Client, [<<"zscore">>, Key, Member])). 215 | 216 | %% TODO: zremrangebyrank 217 | 218 | zremrangebyscore(Client, Key, Min, Max) -> 219 | Cmd = [<<"zremrangebyscore">>, Key, Min, Max], 220 | numeric(erldis_client:sr_scall(Client, Cmd)). 221 | 222 | %% TODO: zunionstore, zinterstore 223 | 224 | %%%%%%%%%%%%%%%%%%% 225 | %% Hash commands %% 226 | %%%%%%%%%%%%%%%%%%% 227 | 228 | hset(Client, Key, Field, Value) -> 229 | erldis_client:sr_scall(Client, [<<"hset">>, Key, Field, Value]). 230 | 231 | hget(Client, Key, Field) -> 232 | erldis_client:sr_scall(Client, [<<"hget">>, Key, Field]). 233 | 234 | hmset(Client, Key, Fields) -> 235 | erldis_client:sr_scall(Client, [<<"hmset">>, Key | Fields]). 236 | 237 | hincrby(Client, Key, Field, Incr) -> 238 | numeric(erldis_client:sr_scall(Client, [<<"hincrby">>, Key, Field, Incr])). 239 | 240 | hexists(Client, Key, Field) -> 241 | erldis_client:sr_scall(Client, [<<"hexists">>, Key, Field]). 242 | 243 | hdel(Client, Key, Field) -> 244 | erldis_client:sr_scall(Client, [<<"hdel">>, Key, Field]). 245 | 246 | hlen(Client, Key) -> numeric(erldis_client:sr_scall(Client, [<<"hlen">>, Key])). 247 | 248 | hkeys(Client, Key) -> erldis_client:scall(Client, [<<"hkeys">>, Key]). 249 | 250 | %% TODO: hvals 251 | 252 | hgetall(Client, Key) -> erldis_client:scall(Client, [<<"hgetall">>, Key]). 253 | 254 | %%%%%%%%%%%%% 255 | %% Sorting %% 256 | %%%%%%%%%%%%% 257 | 258 | sort(Client, Key) -> erldis_client:scall(Client, [<<"sort">>, Key]). 259 | 260 | % TODO: better support for Extra options (LIMIT, ASC|DESC, BY, GET, STORE) 261 | sort(Client, Key, Extra) when is_binary(Key), is_binary(Extra) -> 262 | ExtraParts = re:split(Extra, <<" ">>), 263 | erldis_client:scall(Client, [<<"sort">>, Key | ExtraParts]). 264 | 265 | %%%%%%%%%%%%%%%%%% 266 | %% Transactions %% 267 | %%%%%%%%%%%%%%%%%% 268 | 269 | get_all_results(Client) -> gen_server2:call(Client, get_all_results). 270 | 271 | set_pipelining(Client, Bool) -> gen_server2:cast(Client, {pipelining, Bool}). 272 | 273 | exec(Client, Fun) -> 274 | case erldis_client:sr_scall(Client, <<"multi">>) of 275 | ok -> 276 | set_pipelining(Client, true), 277 | Fun(Client), 278 | get_all_results(Client), 279 | set_pipelining(Client, false), 280 | erldis_client:scall(Client, <<"exec">>); 281 | _ -> 282 | {error, unsupported} 283 | end. 284 | 285 | %%%%%%%%%%%%% 286 | %% PubSub %% 287 | %%%%%%%%%%%%% 288 | 289 | publish(Client, Channel, Value) -> 290 | numeric(erldis_client:sr_scall(Client, [<<"publish">>, Channel, Value])). 291 | 292 | subscribe(Client, Channel, Pid) -> 293 | Cmd = erldis_proto:multibulk_cmd([<<"subscribe">>, Channel]), 294 | 295 | case erldis_client:subscribe(Client, Cmd, Channel, Pid) of 296 | [<<"subscribe">>, Channel, N] -> numeric(N); 297 | _ -> error 298 | end. 299 | 300 | unsubscribe(Client)-> unsubscribe(Client, <<"">>). 301 | 302 | unsubscribe(Client, Channel) -> 303 | case Channel of 304 | <<"">> -> Args = []; 305 | _ -> Args = [Channel] 306 | end, 307 | 308 | Cmd = erldis_proto:multibulk_cmd([<<"unsubscribe">> | Args]), 309 | 310 | case erldis_client:unsubscribe(Client, Cmd, Channel) of 311 | [<<"unsubscribe">>, FirstChan, N] -> {FirstChan, numeric(N)}; 312 | E -> E 313 | end. 314 | 315 | %%%%%%%%%%%%%%%%%%%%%%%%%% 316 | %% Multiple DB commands %% 317 | %%%%%%%%%%%%%%%%%%%%%%%%%% 318 | 319 | select(Client, Index) -> 320 | erldis_client:sr_scall(Client, [<<"select">>, Index]). 321 | 322 | move(Client, Key, DBIndex) -> 323 | erldis_client:sr_scall(Client, [<<"move">>, Key, DBIndex]). 324 | 325 | flushdb(Client) -> erldis_client:sr_scall(Client, <<"flushdb">>). 326 | 327 | flushall(Client) -> erldis_client:sr_scall(Client, <<"flushall">>). 328 | 329 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 330 | %% Persistence control commands %% 331 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 332 | 333 | save(Client) -> erldis_client:scall(Client, <<"save">>). 334 | 335 | bgsave(Client) -> erldis_client:scall(Client, <<"bgsave">>). 336 | 337 | lastsave(Client) -> erldis_client:scall(Client, <<"lastsave">>). 338 | 339 | shutdown(Client) -> erldis_client:scall(Client, <<"shutdown">>). 340 | 341 | %% TODO: bgrewriteaof 342 | 343 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 344 | %% Remote server control commands %% 345 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 346 | 347 | auth(Client, Password) -> 348 | erldis_client:scall(Client, [<<"auth">>, Password]). 349 | 350 | %% @doc Returns proplist of redis stats 351 | info(Client) -> 352 | F = fun(Tok, Stats) -> 353 | case erldis_proto:parse_stat(Tok) of 354 | undefined -> Stats; 355 | KV -> [KV | Stats] 356 | end 357 | end, 358 | 359 | Info = erldis_client:sr_scall(Client, <<"info">>), 360 | lists:foldl(F, [], string:tokens(binary_to_list(Info), ?EOL)). 361 | 362 | %% TODO: monitor 363 | 364 | slaveof(Client, Host, Port) -> 365 | erldis_client:scall(Client, [<<"slaveof">>, Host, Port]). 366 | 367 | slaveof(Client) -> 368 | erldis_client:scall(Client, [<<"slaveof">>, <<"no one">>]). 369 | 370 | %% TODO: config 371 | 372 | %%%%%%%%%%%%%%%%%%%%%% 373 | %% reply conversion %% 374 | %%%%%%%%%%%%%%%%%%%%%% 375 | 376 | % NOTE: numeric conversion of booleans is a good thing because otherwise 377 | % we would lose boolean results when commands are pipelined 378 | 379 | numeric(false) -> 380 | 0; 381 | numeric(true) -> 382 | 1; 383 | numeric(nil) -> 384 | 0; 385 | numeric(I) when is_binary(I) -> 386 | numeric(binary_to_list(I)); 387 | numeric(I) when is_list(I) -> 388 | try 389 | list_to_integer(I) 390 | catch 391 | error:badarg -> 392 | try list_to_float(I) 393 | catch error:badarg -> I 394 | end 395 | end; 396 | numeric(I) -> 397 | I. 398 | 399 | withscores(L) -> 400 | withscores(L,[]). 401 | withscores([], Acc) -> 402 | lists:reverse(Acc); 403 | withscores([_], _Acc) -> 404 | erlang:error(badarg); 405 | withscores([Member, Score | T], Acc) -> 406 | withscores(T, [{Member, numeric(Score)} | Acc]). 407 | -------------------------------------------------------------------------------- /src/erldis_app.erl: -------------------------------------------------------------------------------- 1 | % japerk: do an erlang application with supervisor for client so that there 2 | % is only one connection. Otherwise was getting issues with gen_tcp hanging 3 | % on connect, even with a Timeout given. 4 | -module(erldis_app). 5 | 6 | -behaviour(application). 7 | 8 | -export([start/2, stop/1]). 9 | 10 | start(_Type, _Args) -> erldis_sup:start_link(). 11 | 12 | stop(Client) when is_pid(Client) -> 13 | erldis:quit(Client); 14 | stop(_State) -> 15 | case erldis_sup:client() of 16 | undefined -> ok; 17 | Client -> erldis:quit(Client) 18 | end. -------------------------------------------------------------------------------- /src/erldis_binaries.erl: -------------------------------------------------------------------------------- 1 | -module(erldis_binaries). 2 | 3 | -export([to_binary/1, join/2, encode_key/1, encode_key_parts/1]). 4 | 5 | to_binary(X) when is_list(X) -> list_to_binary(X); 6 | to_binary(X) when is_atom(X) -> list_to_binary(atom_to_list(X)); 7 | to_binary(X) when is_binary(X) -> X; 8 | to_binary(X) when is_integer(X) -> list_to_binary(integer_to_list(X)); 9 | to_binary(X) when is_float(X) -> list_to_binary(float_to_list(X)); 10 | to_binary(X) -> term_to_binary(X). 11 | 12 | join([], _)-> 13 | <<>>; 14 | join(Array, Sep) when not is_binary(Sep) -> 15 | join(Array, to_binary(Sep)); 16 | join(Array, Sep)-> 17 | F = fun(Elem, Acc) -> 18 | E2 = to_binary(Elem), 19 | <> 20 | end, 21 | 22 | Sz = size(Sep), 23 | <<_:Sz/bytes, Result/binary>> = lists:foldl(F, <<>>, Array), 24 | Result. 25 | 26 | encode_key(Key) -> re:replace(Key, <<" ">>, <<"_">>, [{return, binary}]). 27 | 28 | encode_key_parts(Parts) -> encode_key(join(Parts, <<":">>)). -------------------------------------------------------------------------------- /src/erldis_client.erl: -------------------------------------------------------------------------------- 1 | %% @doc This is a very similar to erldis_client, but it does 2 | %% synchronous calls instead of async pipelining. Does so by keeping a queue 3 | %% of From pids in State.calls, then calls gen_server2:reply when it receives 4 | %% handle_info({tcp, ...). Therefore, it must get commands on handle_call 5 | %% instead of handle_cast, which requires direct command sending instead of 6 | %% using the API in erldis. 7 | %% 8 | %% @todo Much of the code has been copied from erldis_client and should be 9 | %% abstracted & shared where possible 10 | %% 11 | %% @author Jacob Perkins 12 | -module(erldis_client). 13 | 14 | -behaviour(gen_server2). 15 | 16 | -include("erldis.hrl"). 17 | 18 | -export([sr_scall/2, scall/2, scall/3, call/2, call/3, bcall/3, 19 | set_call/4, send/3]). 20 | -export([stop/1, transact/1, transact/2, select/2]). 21 | -export([connect/0, connect/1, connect/2, connect/3, connect/4]). 22 | -export([start_link/0, start_link/1, start_link/2, start_link/3, start_link/4]). 23 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 24 | terminate/2, code_change/3]). 25 | -export([subscribe/4, unsubscribe/3]). 26 | 27 | -define(default_timeout, 5000). %% same as in gen.erl in stdlib 28 | 29 | %%%%%%%%%%%%% 30 | %% helpers %% 31 | %%%%%%%%%%%%% 32 | 33 | trim2({ok, S}) -> 34 | Read = size(S)-2, 35 | <> = S, 36 | R; 37 | trim2(S) -> 38 | trim2({ok, S}). 39 | 40 | app_get_env(AppName, Varname, Default) -> 41 | case application:get_env(AppName, Varname) of 42 | undefined -> {ok, Default}; 43 | V -> V 44 | end. 45 | 46 | %%%%%%%%%%%%%%%%%%% 47 | %% call commands %% 48 | %%%%%%%%%%%%%%%%%%% 49 | 50 | select(Client, DB) -> 51 | erldis:select(Client, DB), 52 | Client. 53 | 54 | sr_scall(Client, Args) -> 55 | case scall(Client, Args) of 56 | [R] -> R; 57 | [] -> nil; 58 | ok -> ok 59 | end. 60 | 61 | % This is the simple send with a single row of commands 62 | scall(Client, Args) -> scall(Client, Args, ?default_timeout). 63 | 64 | scall(Client, Args, Timeout) -> 65 | send(Client, erldis_proto:multibulk_cmd(Args), Timeout). 66 | 67 | % This is the complete send with multiple rows 68 | call(Client, Args) -> call(Client, Args, ?default_timeout). 69 | 70 | call(Client, Args, Timeout) -> send(Client, 71 | erldis_proto:multibulk_cmd(Args), Timeout). 72 | 73 | % Blocking call with server-side timeout added as final command arg 74 | bcall(Client, Args, Timeout) -> 75 | scall(Client, Args ++ [server_timeout(Timeout)], erlang_timeout(Timeout)). 76 | 77 | set_call(Client, Cmd, Key, Val) when is_binary(Val) -> 78 | call(Client, Cmd, [[Key, erlang:size(Val)], [Val]]); 79 | set_call(Client, Cmd, Key, Val) -> 80 | set_call(Client, Cmd, Key, erldis_binaries:to_binary(Val)). 81 | 82 | subscribe(Client, Cmd, Class, Pid)-> 83 | case gen_server2:call(Client, {subscribe, Cmd, Class, Pid}, ?default_timeout) of 84 | {error, Reason} -> throw({error, Reason}); 85 | Retval -> Retval 86 | end. 87 | 88 | unsubscribe(Client, Cmd, Class)-> 89 | case gen_server2:call(Client, {unsubscribe, Cmd, Class}, ?default_timeout) of 90 | {error, Reason} -> throw({error, Reason}); 91 | Retval -> Retval 92 | end. 93 | 94 | % Erlang uses milliseconds, with symbol "infinity" for "wait forever"; 95 | % redis uses seconds, with 0 for "wait forever". 96 | server_timeout(infinity) -> 0; 97 | server_timeout(V) when is_number(V) -> V / 1000. 98 | 99 | % Kludge on a few milliseconds to the timeout we gave the server, to 100 | % give the network and client a chance to catch up. 101 | erlang_timeout(infinity) -> infinity; 102 | erlang_timeout(V) when is_number(V) -> V + ?default_timeout. 103 | 104 | send(Client, Cmd, Timeout) -> 105 | Piped = gen_server2:call(Client, is_pipelined), 106 | 107 | if 108 | Piped -> 109 | gen_server2:cast(Client, {send, Cmd}); 110 | true -> 111 | case gen_server2:call(Client, {send, Cmd}, Timeout) of 112 | {error, Reason} -> throw({error, Reason}); 113 | Retval -> Retval 114 | end 115 | end. 116 | 117 | % TODO: use multi/exec for transact (and move transact to erldis.erl) 118 | transact(F) -> 119 | case connect() of 120 | {error, Error} -> {error, Error}; 121 | {ok, Client} -> transact(Client, F) 122 | end. 123 | 124 | transact(DB, F) when is_integer(DB) -> 125 | case connect(DB) of 126 | {error, Error} -> {error, Error}; 127 | {ok, Client} -> transact(Client, F) 128 | end; 129 | transact(Client, F) when is_pid(Client) -> 130 | try F(Client) of 131 | Result -> stop(Client), Result 132 | catch 133 | throw:Result -> stop(Client), throw(Result); 134 | error:Result -> stop(Client), {error, Result}; 135 | exit:Result -> stop(Client), exit(Result) 136 | end. 137 | 138 | %%%%%%%%%%%%%%%%%%%% 139 | %% connect & init %% 140 | %%%%%%%%%%%%%%%%%%%% 141 | 142 | connect() -> start(false). 143 | connect(Host) when is_list(Host) -> start(Host, false); 144 | connect(DB) when is_integer(DB) -> start(DB, false). 145 | connect(Host, Port) -> start(Host, Port, false). 146 | connect(Host, Port, Options) -> start(Host, Port, Options, false). 147 | connect(Host, Port, Options, DB) -> start(Host, Port, Options, DB, false). 148 | 149 | start_link() -> start(true). 150 | start_link(Host) when is_list(Host) -> start(Host, true); 151 | start_link(DB) when is_integer(DB) -> start(DB, true). 152 | start_link(Host, Port) -> start(Host, Port, true). 153 | start_link(Host, Port, Options) -> start(Host, Port, Options, true). 154 | start_link(Host, Port, Options, DB) -> start(Host, Port, Options, DB, true). 155 | 156 | start(ShouldLink) -> 157 | {ok, Host} = app_get_env(erldis, host, "localhost"), 158 | start(Host, ShouldLink). 159 | 160 | start(Host, ShouldLink) when is_list(Host) -> 161 | {ok, Port} = app_get_env(erldis, port, 6379), 162 | start(Host, Port, ShouldLink); 163 | start(DB, ShouldLink) when is_integer(DB) -> 164 | case start(ShouldLink) of 165 | {ok, Client} -> {ok, select(Client, DB)}; 166 | Other -> Other 167 | end. 168 | 169 | start(Host, Port, ShouldLink) -> 170 | {ok, Timeout} = app_get_env(erldis, timeout, 500), 171 | start(Host, Port, [{timeout, Timeout}], ShouldLink). 172 | 173 | start(Host, Port, Options, false) -> 174 | % not using start_link because caller may not want to crash if this 175 | % server is shutdown 176 | gen_server2:start(?MODULE, [Host, Port], Options); 177 | start(Host, Port, Options, true) -> 178 | gen_server2:start_link(?MODULE, [Host, Port], Options). 179 | 180 | start(Host, Port, Options, DB, ShouldLink) -> 181 | case start(Host, Port, Options, ShouldLink) of 182 | {ok, Client} -> {ok, select(Client, DB)}; 183 | Other -> Other 184 | end. 185 | 186 | % stop is synchronous so can be sure that client is shutdown 187 | stop(Client) -> gen_server2:call(Client, disconnect). 188 | 189 | init([Host, Port]) -> 190 | process_flag(trap_exit, true), 191 | {ok, Timeout} = app_get_env(erldis, timeout, 500), 192 | State = #redis{calls=queue:new(), host=Host, port=Port, timeout=Timeout, subscribers=dict:new()}, 193 | 194 | case connect_socket(State, once) of 195 | {error, Why} -> {stop, {socket_error, Why}}; 196 | {ok, NewState} -> {ok, NewState} 197 | end. 198 | 199 | ensure_started(#redis{socket=undefined, db=DB}=State) -> 200 | case connect_socket(State, false) of 201 | {error, Why} -> 202 | Report = [{?MODULE, unable_to_connect}, {error, Why}, State], 203 | error_logger:warning_report(Report), 204 | State; 205 | {ok, NewState} -> 206 | Socket = NewState#redis.socket, 207 | 208 | if 209 | DB == <<"0">> -> 210 | ok; 211 | true -> 212 | % send & recv here since don't have an active socket 213 | % because we want synchronous result since this is called 214 | % from handle_* functions 215 | Cmd = erldis_proto:multibulk_cmd([<<"select">>, DB]), 216 | gen_tcp:send(Socket, Cmd), 217 | {ok, <<"+OK", ?EOL>>} = gen_tcp:recv(Socket, 0) 218 | end, 219 | 220 | inet:setopts(Socket, [{active, once}]), 221 | NewState 222 | end; 223 | ensure_started(State)-> 224 | State. 225 | 226 | connect_socket(#redis{socket=undefined, host=Host, port=Port, timeout=Timeout}=State, Active) -> 227 | % NOTE: send_timeout_close not used because causes {error, badarg} 228 | Opts = [binary, {active, Active}, {packet, line}, {nodelay, true}, 229 | {send_timeout, Timeout}], 230 | % without timeout, default is infinity 231 | case gen_tcp:connect(Host, Port, Opts, Timeout) of 232 | {ok, Socket} -> {ok, State#redis{socket=Socket}}; 233 | {error, Why} -> {error, Why} 234 | end; 235 | connect_socket(State, _) -> 236 | {ok, State}. 237 | 238 | %%%%%%%%%%%%%%%%% 239 | %% handle_call %% 240 | %%%%%%%%%%%%%%%%% 241 | 242 | % Solves issue of remaining getting reset while still accumulating multi-bulk 243 | % reply 244 | dont_reset_remaining(State, Queue) -> 245 | case State#redis.remaining of 246 | 0 -> State#redis{calls=Queue, remaining=1}; 247 | _ -> State#redis{calls=Queue} 248 | end. 249 | 250 | dont_reset_remaining(State, Queue, DB) -> 251 | case State#redis.remaining of 252 | 0 -> State#redis{calls=Queue, remaining=1, db=DB}; 253 | _ -> State#redis{calls=Queue, db=DB} 254 | end. 255 | 256 | handle_call(is_pipelined, _From, State)-> 257 | {reply, State#redis.pipeline, State}; 258 | handle_call(get_all_results, From, #redis{pipeline=true, calls=Calls}=State) -> 259 | case queue:len(Calls) of 260 | 0 -> 261 | % answers came earlier than we could start listening... 262 | % Very unlikely but totally possible. 263 | Reply = lists:reverse(State#redis.results), 264 | {reply, Reply, State#redis{results=[], calls=Calls}}; 265 | _ -> 266 | % We are here earlier than results came, so just make 267 | % ourselves wait until stuff is ready. 268 | R = fun(V) -> gen_server2:reply(From, V) end, 269 | {noreply, State#redis{reply_caller=R}} 270 | end; 271 | handle_call({send, Cmd}, From, State1) -> 272 | % NOTE: redis ignores sent commands it doesn't understand, which means 273 | % we don't get a reply, which means callers will timeout 274 | State = ensure_started(State1), 275 | 276 | case gen_tcp:send(State#redis.socket, [Cmd | <>]) of 277 | ok -> 278 | Queue = queue:in(From, State#redis.calls), 279 | 280 | case Cmd of 281 | % TODO: is there a cleaner way of extracting select DB command 282 | % from multi-bulk commands? 283 | [_, [[<<"$">>, _, <>, <<"select">>, <>], 284 | [<<"$">>, _, <>, DB, <>]]] -> 285 | {noreply, dont_reset_remaining(State, Queue, DB)}; 286 | _ -> 287 | {noreply, dont_reset_remaining(State, Queue)} 288 | end; 289 | {error, Reason} -> 290 | %error_logger:error_report([{send, Cmd}, {error, Reason}]), 291 | {stop, timeout, {error, Reason}, State} 292 | end; 293 | handle_call({subscribe, Cmd, Class, Pid}, From, State1)-> 294 | State = ensure_started(State1), 295 | 296 | case gen_tcp:send(State#redis.socket, [Cmd | <>]) of 297 | ok -> 298 | Queue = queue:in(From, State#redis.calls), 299 | Subscribers = dict:store(Class, Pid, State#redis.subscribers), 300 | {noreply, State#redis{calls=Queue, remaining=1, subscribers=Subscribers}}; 301 | {error, Reason} -> 302 | error_logger:error_report([{send, Cmd}, {error, Reason}]), 303 | {stop, timeout, {error, Reason}, State} 304 | end; 305 | handle_call({unsubscribe, Cmd, Class}, From, State1)-> 306 | State = ensure_started(State1), 307 | 308 | case gen_tcp:send(State#redis.socket, [Cmd | <>]) of 309 | ok -> 310 | Queue = queue:in(From, State#redis.calls), 311 | 312 | if 313 | Class == <<"">> -> 314 | Subscribers = dict:new(); 315 | true -> 316 | Subscribers = dict:erase(Class, State#redis.subscribers) 317 | end, 318 | 319 | {noreply, State#redis{calls=Queue, remaining=1, subscribers=Subscribers}}; 320 | {error, Reason} -> 321 | error_logger:error_report([{send, Cmd}, {error, Reason}]), 322 | {stop, timeout, {error, Reason}, State} 323 | end; 324 | handle_call(disconnect, _, State) -> 325 | {stop, shutdown, shutdown, State}; 326 | handle_call(_, _, State) -> 327 | {reply, undefined, State}. 328 | 329 | %%%%%%%%%%%%%%%%% 330 | %% handle_cast %% 331 | %%%%%%%%%%%%%%%%% 332 | 333 | handle_cast({pipelining, Bool}, State) -> 334 | {noreply, State#redis{pipeline=Bool}}; 335 | handle_cast(disconnect, State) -> 336 | {stop, shutdown, State}; 337 | handle_cast({send, Cmd}, #redis{remaining=Remaining, calls=Calls} = State1) -> 338 | State = ensure_started(State1), 339 | Queue = queue:in(async, Calls), 340 | gen_tcp:send(State#redis.socket, [Cmd | <>]), 341 | 342 | case Remaining of 343 | 0 -> {noreply, State#redis{remaining=1, calls=Queue}}; 344 | _ -> {noreply, State#redis{calls=Queue}} 345 | end; 346 | handle_cast(_, State) -> 347 | {noreply, State}. 348 | 349 | %%%%%%%%%%%%%%%%% 350 | %% handle_info %% 351 | %%%%%%%%%%%%%%%%% 352 | 353 | recv_value(Socket, NBytes) -> 354 | inet:setopts(Socket, [{packet, 0}]), % go into raw mode to read bytes 355 | 356 | case gen_tcp:recv(Socket, NBytes+2) of 357 | {ok, Packet} -> 358 | % error_logger:error_report({line, Packet, NBytes}), 359 | inet:setopts(Socket, [{packet, line}]), % go back to line mode 360 | trim2(Packet); 361 | {error, Reason} -> 362 | error_logger:error_report([{recv, NBytes}, {error, Reason}]), 363 | throw({error, Reason}) 364 | end. 365 | 366 | send_reply(#redis{pipeline=true, calls=Calls, results=Results, reply_caller=ReplyCaller}=State)-> 367 | case lists:reverse(State#redis.buffer) of 368 | [Result] when is_atom(Result) -> Result; 369 | Result -> Result 370 | end, 371 | 372 | {_, Queue} = queue:out(Calls), 373 | 374 | case queue:len(Queue) of 375 | 0 -> 376 | FullResults = [Result|Results], 377 | 378 | NewState = case ReplyCaller of 379 | undefined -> 380 | State#redis{results=FullResults}; 381 | _ -> 382 | ReplyCaller(lists:reverse(FullResults)), 383 | State#redis{results=[]} 384 | end, 385 | 386 | NewState#redis{ 387 | remaining=0, pstate=empty, reply_caller=undefined, 388 | buffer=[], calls=Queue 389 | }; 390 | _ -> 391 | State#redis{ 392 | results=[Result|Results], remaining=1, pstate=empty, 393 | buffer=[], calls=Queue 394 | } 395 | end; 396 | 397 | send_reply(State) -> 398 | case queue:out(State#redis.calls) of 399 | {{value, From}, Queue} -> 400 | Reply = lists:reverse(State#redis.buffer), 401 | gen_server2:reply(From, Reply); 402 | {empty, Queue} -> 403 | ok 404 | end, 405 | 406 | State#redis{calls=Queue, buffer=[], pstate=empty}. 407 | 408 | parse_state(State, Socket, Data) -> 409 | Parse = erldis_proto:parse(State#redis.pstate, trim2(Data)), 410 | % error_logger:error_msg({trimmed, trim2(Data), parsed, Parse}), 411 | case {State#redis.remaining-1, Parse} of 412 | {0, error} -> 413 | % next line is the error string 414 | State#redis{remaining=1, pstate=error}; 415 | {0, {hold, nil}} -> 416 | % reply with values in buffer 417 | send_reply(State); 418 | {0, {hold, Remaining}} -> 419 | % begin accumulation of multi bulk reply 420 | State#redis{remaining=Remaining, pstate=read}; 421 | {N, {read, nil}} -> 422 | % reply with nil 423 | case State#redis.pstate of 424 | empty -> send_reply(State#redis{buffer=[nil]}); 425 | read -> NewBuffer = [nil | State#redis.buffer], 426 | NewState = 427 | State#redis{remaining=N, 428 | buffer=NewBuffer, 429 | pstate=read}, 430 | case N of 431 | 0 -> send_reply(NewState); 432 | _ -> NewState 433 | end 434 | end; 435 | {_, {read, 0}} when State#redis.pstate =:= empty -> 436 | % this is needed to handle single-line reply empty responses 437 | send_reply(State#redis{buffer=[]}); 438 | {0, {read, NBytes}} -> 439 | % reply with Value added to buffer 440 | Value = recv_value(Socket, NBytes), 441 | 442 | case [Value | State#redis.buffer] of 443 | [PubSubValue, Class, <<"message">>] -> 444 | case dict:find(Class, State#redis.subscribers) of 445 | {ok, Pid} -> 446 | Pid ! {message, Class, PubSubValue}; 447 | _ -> 448 | error_logger:error_report([lost_message, {class, Class}]) 449 | end, 450 | 451 | send_reply(State#redis{buffer=[]}); 452 | Buffer -> 453 | send_reply(State#redis{buffer=Buffer}) 454 | end; 455 | {N, {read, NBytes}} -> 456 | % accumulate multi bulk reply 457 | Value = recv_value(Socket, NBytes), 458 | Buffer = [Value | State#redis.buffer], 459 | State#redis{remaining=N, buffer=Buffer, pstate=read}; 460 | {0, Value} -> 461 | % reply with Value 462 | Buffer = [Value | State#redis.buffer], 463 | send_reply(State#redis{buffer=Buffer}); 464 | {N, Value} -> 465 | Buffer = [Value | State#redis.buffer], 466 | State#redis{remaining=N, buffer=Buffer, pstate=read} 467 | end. 468 | 469 | handle_info({tcp, Socket, Data}, State) -> 470 | %error_logger:error_report([{data, Data}, {state, State}]), 471 | case parse_state(State, Socket, Data) of 472 | {error, Reason} -> 473 | {stop, Reason, State}; 474 | NewState -> 475 | inet:setopts(Socket, [{active, once}]), 476 | {noreply, NewState} 477 | end; 478 | handle_info({tcp_closed, Socket}, State=#redis{socket=Socket}) -> 479 | {noreply, State#redis{socket=undefined}}; 480 | handle_info(_Info, State) -> 481 | {noreply, State}. 482 | 483 | %%%%%%%%%%%%%%% 484 | %% terminate %% 485 | %%%%%%%%%%%%%%% 486 | 487 | terminate(_Reason, State) -> 488 | % NOTE: if supervised with brutal_kill, may not be able to reply 489 | R = fun(From) -> gen_server2:reply(From, {error, closed}) end, 490 | lists:foreach(R, queue:to_list(State#redis.calls)), 491 | 492 | case State#redis.socket of 493 | undefined -> ok; 494 | Socket -> gen_tcp:close(Socket) 495 | end. 496 | 497 | code_change(_OldVsn, State, _Extra) -> {ok, State}. 498 | -------------------------------------------------------------------------------- /src/erldis_dict.erl: -------------------------------------------------------------------------------- 1 | -module(erldis_dict). 2 | 3 | -export([append/3, append_list/3, erase/2, fetch/2, fetch_keys/2, find/2, 4 | is_key/2, size/1, store/3, update/3, update/4, 5 | update_counter/2, update_counter/3]). 6 | 7 | % NOTE: use erldis_lists instead, fetch & find won't work for lists 8 | append(Key, Value, Client) -> erldis:rpush(Client, Key, Value). 9 | 10 | append_list(Key, Values, Client) -> 11 | lists:foreach(fun(Value) -> append(Key, Value, Client) end, Values). 12 | 13 | erase(Key, Client) -> erldis:del(Client, Key). 14 | 15 | fetch(Key, Client) -> 16 | case erldis:get(Client, Key) of 17 | nil -> undefined; 18 | Value -> Value 19 | end. 20 | 21 | % NOTE: this is only useful if keys have a known prefix 22 | fetch_keys(Pattern, Client) -> erldis:keys(Client, Pattern). 23 | 24 | %filter(Pred, Client) -> ok. 25 | 26 | find(Key, Client) -> 27 | case fetch(Key, Client) of 28 | undefined -> error; 29 | Value -> {ok, Value} 30 | end. 31 | 32 | %fold(Fun, Acc0, Client) -> ok. 33 | 34 | %from_list(List, Client) -> ok. 35 | 36 | is_key(Key, Client) -> erldis:exists(Client, Key). 37 | 38 | size(Client) -> erldis:dbsize(Client). 39 | 40 | store(Key, [], Client) -> erase(Key, Client); 41 | store(Key, Value, Client) -> erldis:set(Client, Key, Value). 42 | 43 | %to_list(Client) -> ok. 44 | 45 | % NOTE: update/3 & update/4 are not atomic 46 | 47 | update(Key, Fun, Client) -> store(Key, Fun(fetch(Key, Client)), Client). 48 | 49 | update(Key, Fun, Initial, Client) -> 50 | case find(Key, Client) of 51 | {ok, Value} -> store(Key, Fun(Value), Client); 52 | error -> store(Key, Initial, Client) 53 | end. 54 | 55 | update_counter(Key, Client) -> update_counter(Key, 1, Client). 56 | 57 | % NOTE: this returns new count value, not a modified dict 58 | update_counter(Key, 1, Client) -> erldis:incr(Client, Key); 59 | update_counter(Key, Incr, Client) -> erldis:incrby(Client, Key, Incr). -------------------------------------------------------------------------------- /src/erldis_list.erl: -------------------------------------------------------------------------------- 1 | -module(erldis_list). 2 | 3 | % original queue 4 | -export([is_queue/2, is_empty/2, len/2, in/3, in_r/3, out/2, out_r/2]). 5 | % extra queue 6 | -export([out_foreach/3]). 7 | % extended queue 8 | -export([get/2, get_r/2, drop/2, drop_r/2, peek/2, peek_r/2]). 9 | % array 10 | -export([get/3, is_array/2, set/4, size/2]). 11 | % list 12 | -export([delete/3, foreach/3, is_list/2, last/2, merge/4, nth/3, 13 | sublist/3, sublist/4, umerge/4]). 14 | % common 15 | -export([foldl/4, foldr/4, from_list/3, to_list/2, reverse/2]). 16 | 17 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%% 18 | %% original queue like api %% 19 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%% 20 | 21 | is_queue(Key, Client) -> is_list(Key, Client). 22 | 23 | is_empty(Key, Client) -> len(Key, Client) == 0. 24 | 25 | len(Key, Client) -> erldis:llen(Client, Key). 26 | 27 | in(Item, Key, Client) -> 28 | erldis:rpush(Client, Key, Item). 29 | 30 | in_r(Item, Key, Client) -> 31 | erldis:lpush(Client, Key, Item). 32 | 33 | out(Key, Client) -> 34 | case erldis:lpop(Client, Key) of 35 | nil -> empty; 36 | Item -> {value, Item} 37 | end. 38 | 39 | out_r(Key, Client) -> 40 | case erldis:rpop(Client, Key) of 41 | nil -> empty; 42 | Item -> {value, Item} 43 | end. 44 | 45 | %% @doc Call F on each element in queue until it's empty. 46 | out_foreach(F, Key, Client) -> 47 | case out(Key, Client) of 48 | empty -> 49 | ok; 50 | {value, Item} -> 51 | F(Item), 52 | out_foreach(F, Key, Client) 53 | end. 54 | 55 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%% 56 | %% extended queue like api %% 57 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%% 58 | 59 | get(Key, Client) -> 60 | case get(0, Key, Client) of 61 | nil -> empty; 62 | Item -> Item 63 | end. 64 | 65 | get_r(Key, Client) -> 66 | Len = len(Key, Client), 67 | 68 | if 69 | Len < 1 -> 70 | empty; 71 | true -> 72 | case get(Len - 1, Key, Client) of 73 | nil -> empty; 74 | Item -> Item 75 | end 76 | end. 77 | 78 | drop(Key, Client) -> out(Key, Client). 79 | 80 | drop_r(Key, Client) -> out_r(Key, Client). 81 | 82 | peek(Key, Client) -> 83 | case get(0, Key, Client) of 84 | nil -> empty; 85 | Item -> {value, Item} 86 | end. 87 | 88 | peek_r(Key, Client) -> 89 | case get_r(Key, Client) of 90 | empty -> empty; 91 | Item -> {value, Item} 92 | end. 93 | 94 | %%%%%%%%%%%%%%%%%%%% 95 | %% array like api %% 96 | %%%%%%%%%%%%%%%%%%%% 97 | 98 | get(I, Key, Client) -> erldis:lindex(Client, Key, I). 99 | 100 | is_array(Key, Client) -> is_list(Key, Client). 101 | 102 | set(I, Value, Key, Client) -> erldis:lset(Client, Key, I, Value). 103 | 104 | size(Key, Client) -> len(Key, Client). 105 | 106 | %%%%%%%%%%%%%%%%%%%% 107 | %% lists like api %% 108 | %%%%%%%%%%%%%%%%%%%% 109 | 110 | % all 111 | % any 112 | % append 113 | 114 | delete(Elem, Key, Client) -> erldis:lrem(Client, Key, 1, Elem). 115 | 116 | % dropwhile 117 | 118 | foreach(F, Key, Client) -> foreach(0, F, Key, Client). 119 | 120 | foreach(I, F, Key, Client) -> 121 | case get(I, Key, Client) of 122 | nil -> ok; 123 | Item -> F(Item), foreach(I+1, F, Key, Client) 124 | end. 125 | 126 | is_list(Key, Client) -> <<"list">> == erldis:type(Client, Key). 127 | 128 | % keysort 129 | 130 | last(Key, Client) -> get_r(Key, Client). 131 | 132 | merge(F, L, Key, Client) -> merge(0, F, L, Key, Client). 133 | 134 | merge(_, _, [], _, _) -> 135 | ok; 136 | merge(I, F, L, Key, Client) -> 137 | case get(I, Key, Client) of 138 | % append the rest of the list 139 | nil -> lists:foreach(fun(Item) -> in(Item, Key, Client) end, L); 140 | % compare A to head of L 141 | A -> merge(I, F, A, L, Key, Client) 142 | end. 143 | 144 | merge(I, F, A, [B | L], Key, Client) -> 145 | case F(A, B) of 146 | true -> 147 | % B comes after A, so continue iterating 148 | merge(I+1, F, [B | L], Key, Client); 149 | false -> 150 | % B comes before A, so replace A with B and continue merging with 151 | % A merged into L 152 | set(I, B, Key, Client), 153 | merge(I+1, F, lists:merge(F, [A], L), Key, Client) 154 | end. 155 | 156 | nth(N, Key, Client) -> get(N, Key, Client). 157 | 158 | % nthtail 159 | 160 | sublist(Key, Client, Len) -> sublist(Key, Client, 1, Len). 161 | 162 | sublist(Key, Client, Start, 1) -> 163 | case get(Start, Key, Client) of 164 | nil -> []; 165 | Elem -> [Elem] 166 | end; 167 | sublist(Key, Client, Start, Len) when Start > 0, Len > 1 -> 168 | % erlang lists are 1-indexed 169 | erldis:lrange(Client, Key, Start - 1, Start + Len - 2); 170 | sublist(Key, Client, Start, Len) when Start < 0, Len > 1 -> 171 | % can give a negative start where -1 is the last element 172 | erldis:lrange(Client, Key, Start, Start - Len + 1). 173 | 174 | % sort 175 | % takewhile 176 | 177 | umerge(F, L, Key, Client) -> umerge(0, F, L, Key, Client). 178 | 179 | umerge(_, _, [], _, _) -> 180 | ok; 181 | umerge(I, F, L, Key, Client) -> 182 | case get(I, Key, Client) of 183 | nil -> lists:foreach(fun(Item) -> in(Item, Key, Client) end, L); 184 | A -> umerge(I, F, A, L, Key, Client) 185 | end. 186 | 187 | umerge(I, F, A, [B | L], Key, Client) when A == B -> 188 | umerge(I+1, F, L, Key, Client); 189 | umerge(I, F, A, [B | L], Key, Client) -> 190 | case F(A, B) of 191 | true -> 192 | umerge(I+1, F, [B | L], Key, Client); 193 | false -> 194 | set(I, B, Key, Client), 195 | umerge(I+1, F, lists:umerge(F, [A], L), Key, Client) 196 | end. 197 | 198 | %%%%%%%%%%%% 199 | %% common %% 200 | %%%%%%%%%%%% 201 | 202 | % TODO: foldl, foldr, to_list, foreach, and any other iterative functions 203 | % would probably be much faster and more efficient if they iterated on 204 | % fixed sized chunks using sublist, since each call to get/3 is avg O(n) 205 | % except when I is the first or last element. 206 | 207 | foldl(F, Acc0, Key, Client) -> foldl(0, F, Acc0, Key, Client). 208 | 209 | foldl(I, F, Acc0, Key, Client) -> 210 | case get(I, Key, Client) of 211 | nil -> Acc0; 212 | Item -> foldl(I+1, F, F(Item, Acc0), Key, Client) 213 | end. 214 | 215 | foldr(F, Acc0, Key, Client) -> foldr(len(Key, Client) - 1, F, Acc0, Key, Client). 216 | 217 | foldr(I, _, Acc0, _, _) when I < 0 -> 218 | Acc0; 219 | foldr(I, F, Acc0, Key, Client) -> 220 | case get(I, Key, Client) of 221 | nil -> Acc0; 222 | Item -> foldr(I-1, F, F(Item, Acc0), Key, Client) 223 | end. 224 | 225 | from_list(L, Key, Client) -> 226 | erldis:del(Client, Key), 227 | lists:foreach(fun(Item) -> in(Item, Key, Client) end, L). 228 | 229 | to_list(Key, Client) -> foldr(fun(Item, L) -> [Item | L] end, [], Key, Client). 230 | 231 | reverse(Key, Client) -> foldl(fun(Item, L) -> [Item | L] end, [], Key, Client). 232 | 233 | % filter 234 | % map 235 | % member 236 | -------------------------------------------------------------------------------- /src/erldis_proto.erl: -------------------------------------------------------------------------------- 1 | -module(erldis_proto). 2 | 3 | -include("erldis.hrl"). 4 | 5 | -export([multibulk_cmd/1, parse/2, parse_stat/1]). 6 | 7 | %%%%%%%%%%%%%%%%%% 8 | %% send command %% 9 | %%%%%%%%%%%%%%%%%% 10 | 11 | -define(i2l(X), integer_to_list(X)). 12 | 13 | multibulk_cmd(Args) when is_binary(Args) -> 14 | multibulk_cmd([Args]); 15 | multibulk_cmd(Args) when is_list(Args) -> 16 | TotalLength = length(Args), 17 | ArgCount = [<<"*">>, ?i2l(TotalLength), <>], 18 | Bins = [erldis_binaries:to_binary(B) || B <- Args], 19 | ArgBin = [[<<"$">>, ?i2l(iolist_size(A)), <>, A, <>] || A <- Bins], 20 | [ArgCount, ArgBin]. 21 | 22 | %%%%%%%%%%%%%%%%%%%% 23 | %% parse response %% 24 | %%%%%%%%%%%%%%%%%%%% 25 | 26 | parse(_, <<"+OK">>) -> 27 | ok; 28 | parse(_, <<":0">>) -> 29 | false; 30 | parse(_, <<":1">>) -> 31 | true; 32 | parse(empty, <<"+QUEUED">>) -> 33 | queued; 34 | parse(empty, <<"+PONG">>) -> 35 | pong; 36 | parse(empty, <<"-", Message/binary>>) -> 37 | {error, Message}; 38 | parse(_, <<"$-1">>) -> 39 | {read, nil}; 40 | parse(empty, <<"*-1">>) -> 41 | {hold, nil}; 42 | parse(empty, <<"*0">>) -> 43 | {read, 0}; 44 | parse(_, <<"$", BulkSize/binary>>) -> 45 | {read, list_to_integer(binary_to_list(BulkSize))}; 46 | parse(empty, <<"*", MultiBulkSize/binary>>) -> 47 | {hold, list_to_integer(binary_to_list(MultiBulkSize))}; 48 | parse(read, Message)-> 49 | convert(Message); 50 | parse(empty, Message) -> 51 | convert(Message). 52 | 53 | convert(<<":", Message/binary>>) -> 54 | list_to_integer(binary_to_list(Message)); 55 | % in case the message is not OK or PONG it's a 56 | % real value that we don't know how to convert 57 | % so just pass it as is and remove the + 58 | convert(<<"+", Message/binary>>) -> 59 | Message; 60 | convert(Message) -> 61 | Message. 62 | 63 | %%%%%%%%%%%%%%%%%%%%%%%%% 64 | %% parse info response %% 65 | %%%%%%%%%%%%%%%%%%%%%%%%% 66 | 67 | parse_stat("redis_version:"++Vsn) -> 68 | {version, Vsn}; 69 | parse_stat("uptime_in_seconds:"++Val) -> 70 | {uptime, list_to_integer(Val)}; 71 | parse_stat("connected_clients:"++Val) -> 72 | {clients, list_to_integer(Val)}; 73 | parse_stat("connected_slaves:"++Val) -> 74 | {slaves, list_to_integer(Val)}; 75 | parse_stat("used_memory:"++Val) -> 76 | {memory, list_to_integer(Val)}; 77 | parse_stat("changes_since_last_save:"++Val) -> 78 | {changes, list_to_integer(Val)}; 79 | parse_stat("last_save_time:"++Val) -> 80 | {last_save, list_to_integer(Val)}; 81 | parse_stat("total_connections_received:"++Val) -> 82 | {connections, list_to_integer(Val)}; 83 | parse_stat("total_commands_processed:"++Val) -> 84 | {commands, list_to_integer(Val)}; 85 | parse_stat(_) -> 86 | undefined. 87 | -------------------------------------------------------------------------------- /src/erldis_sets.erl: -------------------------------------------------------------------------------- 1 | %% @doc sets like interface to redis. Uses erldis_client to ensure 2 | %% synchronous results. 3 | %% 4 | %% @author Jacob Perkins 5 | -module(erldis_sets). 6 | 7 | -export([delete/1, is_set/2, size/2, to_list/2, from_list/3, is_element/3, 8 | add_element/3, del_element/3, union/2, intersection/3, intersection/2, 9 | is_disjoint/3, subtract/3, subtract/2, is_subset/3, fold/4, filter/3]). 10 | 11 | %%%%%%%%%%%%%%%%%%% 12 | %% sets-like api %% 13 | %%%%%%%%%%%%%%%%%%% 14 | 15 | delete(Client) -> erldis_client:stop(Client). 16 | 17 | is_set(Client, Key) -> <<"set">> == erldis:type(Client, Key). 18 | 19 | size(Client, Key) -> erldis:scard(Client, Key). 20 | 21 | to_list(Client, Key) -> erldis:smembers(Client, Key). 22 | 23 | from_list(Client, Key, List) -> 24 | % delete existing set 25 | erldis:del(Client, Key), 26 | lists:foreach(fun(Elem) -> add_element(Elem, Client, Key) end, List), 27 | Client. 28 | 29 | is_element(Elem, Client, Key) -> erldis:sismember(Client, Key, Elem). 30 | 31 | add_element(Elem, Client, Key) -> erldis:sadd(Client, Key, Elem). 32 | 33 | del_element(Elem, Client, Key) -> erldis:srem(Client, Key, Elem). 34 | 35 | union(Client, Keys) -> erldis:sunion(Client, Keys). 36 | 37 | intersection(Client, Key1, Key2) -> intersection(Client, [Key1, Key2]). 38 | 39 | intersection(Client, Keys) -> erldis:sintersect(Client, Keys). 40 | 41 | is_disjoint(Client, Key1, Key2) -> [] == intersection(Client, [Key1, Key2]). 42 | 43 | subtract(Client, Key1, Key2) -> subtract(Client, [Key1, Key2]). 44 | 45 | subtract(Client, Keys) -> erldis:sdiff(Client, Keys). 46 | 47 | is_subset(Client, Key1, Key2) -> [] == subtract(Client, [Key2, Key1]). 48 | 49 | fold(F, Acc0, Client, Key) -> lists:foldl(F, Acc0, to_list(Client, Key)). 50 | 51 | filter(Pred, Client, Key) -> lists:filter(Pred, to_list(Client, Key)). 52 | -------------------------------------------------------------------------------- /src/erldis_sup.erl: -------------------------------------------------------------------------------- 1 | -module(erldis_sup). 2 | 3 | -behaviour(supervisor). 4 | 5 | -export([start_link/0, client/0, init/1]). 6 | 7 | start_link() -> 8 | {ok, Pid} = supervisor:start_link({local, ?MODULE}, ?MODULE, []), 9 | {ok, Pid, client()}. 10 | 11 | client() -> 12 | % Client pid() is undefined if not started 13 | [{erldis_client, Client, worker, [erldis_client]}] = supervisor:which_children(?MODULE), 14 | % not trying to restart_child(client) because gen_tcp:connect will hang 15 | % even if timeout is given 16 | Client. 17 | 18 | init(_Args) -> 19 | {ok, {{one_for_one, 1, 60}, [ 20 | % transient restart so client can disconnect safely 21 | % timeout so client has time to disconnect on exit 22 | {erldis_client, {erldis_client, start_link, []}, 23 | transient, 500, worker, [erldis_client]} 24 | ]}}. -------------------------------------------------------------------------------- /src/erldis_sync_client.erl: -------------------------------------------------------------------------------- 1 | %% @doc This is a gen_server very similar to erldis_client, but it does 2 | %% synchronous calls instead of async pipelining. Does so by keeping a queue 3 | %% of From pids in State.calls, then calls gen_server:reply when it receives 4 | %% handle_info({tcp, ...). Therefore, it must get commands on handle_call 5 | %% instead of handle_cast, which requires direct command sending instead of 6 | %% using the API in erldis. 7 | %% 8 | %% @todo Much of the code has been copied from erldis_client and should be 9 | %% abstracted & shared where possible 10 | %% 11 | %% @author Jacob Perkins 12 | -module(erldis_sync_client). 13 | 14 | -behaviour(gen_server). 15 | 16 | -include("erldis.hrl"). 17 | 18 | -export([scall/2, scall/3, call/2, call/3, stop/1, transact/1, transact/2, select/2, info/1, sr_scall/2, sr_scall/3]). 19 | -export([connect/0, connect/1, connect/2, connect/3, connect/4]). 20 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 21 | terminate/2, code_change/3]). 22 | 23 | %%%%%%%%%%%%% 24 | %% helpers %% 25 | %%%%%%%%%%%%% 26 | 27 | % TODO: copied from erldis_client, should be abstracted & shared 28 | 29 | str(X) when is_list(X) -> 30 | X; 31 | str(X) when is_atom(X) -> 32 | atom_to_list(X); 33 | str(X) when is_binary(X) -> 34 | binary_to_list(X); 35 | str(X) when is_integer(X) -> 36 | integer_to_list(X); 37 | str(X) when is_float(X) -> 38 | float_to_list(X). 39 | 40 | format([], Result) -> 41 | string:join(lists:reverse(Result), ?EOL); 42 | format([Line|Rest], Result) -> 43 | JoinedLine = string:join([str(X) || X <- Line], " "), 44 | format(Rest, [JoinedLine|Result]). 45 | 46 | format(Lines) -> 47 | format(Lines, []). 48 | sformat(Line) -> 49 | format([Line], []). 50 | 51 | trim2({ok, S}) -> 52 | string:substr(S, 1, length(S)-2); 53 | trim2(S) -> 54 | trim2({ok, S}). 55 | 56 | app_get_env(AppName, Varname, Default) -> 57 | case application:get_env(AppName, Varname) of 58 | undefined -> 59 | {ok, Default}; 60 | V -> 61 | V 62 | end. 63 | 64 | ensure_started(#redis{socket=undefined, host=Host, port=Port, timeout=Timeout}=State)-> 65 | Opts = [list, {active, once}, {packet, line}, {nodelay, true}, {send_timeout, Timeout}], 66 | 67 | case gen_tcp:connect(Host, Port, Opts, Timeout) of 68 | {ok, Socket} -> 69 | error_logger:info_report([{?MODULE, reconnected}, State]), 70 | State#redis{socket=Socket}; 71 | {error, Why} -> 72 | Report = [{?MODULE, unable_to_connect}, {error, Why}, State], 73 | error_logger:warning_report(Report), 74 | State 75 | end; 76 | ensure_started(State)-> 77 | State. 78 | 79 | %%%%%%%%%%%%%%%%%% 80 | %% call command %% 81 | %%%%%%%%%%%%%%%%%% 82 | sr_scall(Client, Cmd) -> sr_scall(Client, Cmd, []). 83 | sr_scall(Client, Cmd, Args) -> 84 | [R] = scall(Client, Cmd, Args), 85 | R. 86 | 87 | % This is the simple send with a single row of commands 88 | scall(Client, Cmd) -> scall(Client, Cmd, []). 89 | 90 | scall(Client, Cmd, Args) -> 91 | case gen_server:call(Client, {send, sformat([Cmd|Args])}) of 92 | {error, Reason} -> throw({error, Reason}); 93 | Retval -> Retval 94 | end. 95 | 96 | % This is the complete send with multiple rows 97 | call(Client, Cmd) -> call(Client, Cmd, []). 98 | 99 | call(Client, Cmd, Args) -> 100 | SCmd = string:join([str(Cmd), format(Args)], " "), 101 | 102 | case gen_server:call(Client, {send, SCmd}) of 103 | {error, Reason} -> throw({error, Reason}); 104 | Retval -> Retval 105 | end. 106 | 107 | % stop is synchronous so can be sure that client is shutdown 108 | stop(Client) -> gen_server:call(Client, disconnect). 109 | 110 | transact(F) -> 111 | case connect() of 112 | {error, Error} -> {error, Error}; 113 | {ok, Client} -> transact(Client, F) 114 | end. 115 | 116 | transact(DB, F) when is_integer(DB) -> 117 | case connect(DB) of 118 | {error, Error} -> {error, Error}; 119 | {ok, Client} -> transact(Client, F) 120 | end; 121 | transact(Client, F) when is_pid(Client) -> 122 | try F(Client) of 123 | Result -> stop(Client), Result 124 | catch 125 | throw:Result -> stop(Client), throw(Result); 126 | error:Result -> stop(Client), {error, Result}; 127 | exit:Result -> stop(Client), exit(Result) 128 | end. 129 | 130 | select(Client, DB) -> 131 | [ok] = scall(Client, select, [DB]), 132 | Client. 133 | 134 | info(Client) -> 135 | F = fun(Stat) -> 136 | case parse_stat(Stat) of 137 | undefined -> false; 138 | {Key, Val} -> {Key, Val} 139 | end 140 | end, 141 | 142 | [S] = scall(Client, info), 143 | elists:mapfilter(F, string:tokens(S, "\r\n")). 144 | 145 | parse_stat("redis_version:"++Vsn) -> 146 | {version, Vsn}; 147 | parse_stat("uptime_in_seconds:"++Val) -> 148 | {uptime, list_to_integer(Val)}; 149 | parse_stat("connected_clients:"++Val) -> 150 | {clients, list_to_integer(Val)}; 151 | parse_stat("connected_slaves:"++Val) -> 152 | {slaves, list_to_integer(Val)}; 153 | parse_stat("used_memory:"++Val) -> 154 | {memory, list_to_integer(Val)}; 155 | parse_stat("changes_since_last_save:"++Val) -> 156 | {changes, list_to_integer(Val)}; 157 | parse_stat("last_save_time:"++Val) -> 158 | {last_save, list_to_integer(Val)}; 159 | parse_stat("total_connections_received:"++Val) -> 160 | {connections, list_to_integer(Val)}; 161 | parse_stat("total_commands_processed:"++Val) -> 162 | {commands, list_to_integer(Val)}; 163 | parse_stat(_) -> 164 | undefined. 165 | 166 | %%%%%%%%%% 167 | %% init %% 168 | %%%%%%%%%% 169 | 170 | connect() -> 171 | {ok, Host} = app_get_env(erldis, host, "localhost"), 172 | connect(Host). 173 | 174 | connect(Host) when is_list(Host) -> 175 | {ok, Port} = app_get_env(erldis, port, 6379), 176 | connect(Host, Port); 177 | connect(DB) when is_integer(DB) -> 178 | case connect() of 179 | {ok, Client} -> {ok, select(Client, DB)}; 180 | Other -> Other 181 | end. 182 | 183 | connect(Host, Port) -> 184 | {ok, Timeout} = app_get_env(erldis, timeout, 500), 185 | connect(Host, Port, [{timeout, Timeout}]). 186 | 187 | connect(Host, Port, Options) -> 188 | % not using start_link because caller may not want to crash if this 189 | % server is shutdown 190 | gen_server:start(?MODULE, [Host, Port], Options). 191 | 192 | connect(Host, Port, Options, DB) -> 193 | case connect(Host, Port, Options) of 194 | {ok, Client} -> {ok, select(Client, DB)}; 195 | Other -> Other 196 | end. 197 | 198 | init([Host, Port]) -> 199 | process_flag(trap_exit, true), 200 | {ok, Timeout} = app_get_env(erldis, timeout, 500), 201 | % presence of send_timeout_close Opt causes {error, badarg} 202 | Opts = [list, {active, once}, {packet, line}, {nodelay, true}, 203 | {send_timeout, Timeout}], 204 | % without timeout, default is infinity 205 | case gen_tcp:connect(Host, Port, Opts, Timeout) of 206 | {error, Why} -> 207 | {stop, {socket_error, Why}}; 208 | {ok, Socket} -> 209 | % calls is a queue instead of a count 210 | {ok, #redis{socket=Socket, calls=queue:new(), host=Host, port=Port, timeout=Timeout}} 211 | end. 212 | 213 | %%%%%%%%%%%%%%%%% 214 | %% handle_call %% 215 | %%%%%%%%%%%%%%%%% 216 | 217 | handle_call({send, Cmd}, From, State1) -> 218 | % NOTE: redis ignores sent commands it doesn't understand, which means 219 | % we don't get a reply, which means callers will timeout 220 | State = ensure_started(State1), 221 | case gen_tcp:send(State#redis.socket, [Cmd|?EOL]) of 222 | ok -> 223 | %error_logger:info_report([{send, Cmd}, {from, From}]), 224 | Queue = queue:in(From, State#redis.calls), 225 | {noreply, State#redis{calls=Queue, remaining=1}}; 226 | {error, Reason} -> 227 | error_logger:error_report([{send, Cmd}, {error, Reason}]), 228 | {stop, timeout, {error, Reason}, State} 229 | end; 230 | handle_call(disconnect, _, State) -> 231 | {stop, shutdown, shutdown, State}; 232 | handle_call(_, _, State) -> 233 | {reply, undefined, State}. 234 | 235 | handle_cast(disconnect, State) -> 236 | {stop, shutdown, State}; 237 | handle_cast(_, State) -> 238 | {noreply, State}. 239 | 240 | %%%%%%%%%%%%%%%%% 241 | %% handle_info %% 242 | %%%%%%%%%%%%%%%%% 243 | 244 | recv_value(Socket, NBytes) -> 245 | inet:setopts(Socket, [{packet, 0}]), % go into raw mode to read bytes 246 | 247 | case gen_tcp:recv(Socket, NBytes+2) of 248 | {ok, Packet} -> 249 | inet:setopts(Socket, [{packet, line}]), % go back to line mode 250 | trim2({ok, Packet}); 251 | {error, Reason} -> 252 | error_logger:error_report([{recv, NBytes}, {error, Reason}]), 253 | throw({error, Reason}) 254 | end. 255 | 256 | send_reply(State) -> 257 | {{value, From}, Queue} = queue:out(State#redis.calls), 258 | Reply = lists:reverse(State#redis.buffer), 259 | %error_logger:info_report([{reply, Reply}, {to, From}]), 260 | gen_server:reply(From, Reply), 261 | State#redis{calls=Queue, buffer=[], pstate=empty}. 262 | 263 | parse_state(State, Socket, Data) -> 264 | Parse = erldis_proto:parse(State#redis.pstate, trim2(Data)), 265 | case {State#redis.remaining-1, Parse} of 266 | {0, error} -> 267 | % next line is the error string 268 | State#redis{remaining=1, pstate=error}; 269 | {0, {hold, nil}} -> 270 | % reply with values in buffer 271 | send_reply(State); 272 | {0, {hold, Remaining}} -> 273 | % begin accumulation of multi bulk reply 274 | State#redis{remaining=Remaining, pstate=read}; 275 | {_, {read, nil}} -> 276 | % reply with nil 277 | send_reply(State#redis{buffer=[nil]}); 278 | {_, {read, 0}} -> 279 | % reply with nil 280 | send_reply(State#redis{buffer=[]}); 281 | {0, {read, NBytes}} -> 282 | % reply with Value added to buffer 283 | Value = recv_value(Socket, NBytes), 284 | Buffer = [Value | State#redis.buffer], 285 | send_reply(State#redis{buffer=Buffer}); 286 | {N, {read, NBytes}} -> 287 | % accumulate multi bulk reply 288 | Value = recv_value(Socket, NBytes), 289 | Buffer = [Value | State#redis.buffer], 290 | State#redis{remaining=N, buffer=Buffer, pstate=read}; 291 | {0, Value} -> 292 | % reply with Value 293 | send_reply(State#redis{buffer=[Value]}) 294 | end. 295 | 296 | handle_info({tcp, Socket, Data}, State) -> 297 | case (catch parse_state(State, Socket, Data)) of 298 | {error, Reason} -> 299 | error_logger:error_report([{parse_state, Data}, {error, Reason}]), 300 | {stop, Reason, State}; 301 | NewState -> 302 | inet:setopts(Socket, [{active, once}]), 303 | {noreply, NewState} 304 | end; 305 | handle_info({tcp_closed, Socket}, State=#redis{socket=Socket}) -> 306 | error_logger:warning_report([{erldis_sync_client, tcp_closed}, State]), 307 | {noreply, State#redis{socket=undefined}}; 308 | handle_info(_Info, State) -> 309 | {noreply, State}. 310 | 311 | %%%%%%%%%%%%%%% 312 | %% terminate %% 313 | %%%%%%%%%%%%%%% 314 | 315 | terminate(_Reason, State) -> 316 | % NOTE: if supervised with brutal_kill, may not be able to reply 317 | R = fun(From) -> gen_server:reply(From, {error, closed}) end, 318 | lists:foreach(R, queue:to_list(State#redis.calls)), 319 | 320 | case State#redis.socket of 321 | undefined -> ok; 322 | Socket -> gen_tcp:close(Socket) 323 | end. 324 | 325 | code_change(_OldVsn, State, _Extra) -> {ok, State}. -------------------------------------------------------------------------------- /src/gen_server2.erl: -------------------------------------------------------------------------------- 1 | %% This file is a copy of gen_server.erl from the R11B-5 Erlang/OTP 2 | %% distribution, with the following modifications: 3 | %% 4 | %% 1) the module name is gen_server2 5 | %% 6 | %% 2) more efficient handling of selective receives in callbacks 7 | %% gen_server2 processes drain their message queue into an internal 8 | %% buffer before invoking any callback module functions. Messages are 9 | %% dequeued from the buffer for processing. Thus the effective message 10 | %% queue of a gen_server2 process is the concatenation of the internal 11 | %% buffer and the real message queue. 12 | %% As a result of the draining, any selective receive invoked inside a 13 | %% callback is less likely to have to scan a large message queue. 14 | %% 15 | %% 3) gen_server2:cast is guaranteed to be order-preserving 16 | %% The original code could reorder messages when communicating with a 17 | %% process on a remote node that was not currently connected. 18 | %% 19 | %% All modifications are (C) 2009 LShift Ltd. 20 | 21 | %% ``The contents of this file are subject to the Erlang Public License, 22 | %% Version 1.1, (the "License"); you may not use this file except in 23 | %% compliance with the License. You should have received a copy of the 24 | %% Erlang Public License along with this software. If not, it can be 25 | %% retrieved via the world wide web at http://www.erlang.org/. 26 | %% 27 | %% Software distributed under the License is distributed on an "AS IS" 28 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 29 | %% the License for the specific language governing rights and limitations 30 | %% under the License. 31 | %% 32 | %% The Initial Developer of the Original Code is Ericsson Utvecklings AB. 33 | %% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings 34 | %% AB. All Rights Reserved.'' 35 | %% 36 | %% $Id$ 37 | %% 38 | -module(gen_server2). 39 | 40 | %%% --------------------------------------------------- 41 | %%% 42 | %%% The idea behind THIS server is that the user module 43 | %%% provides (different) functions to handle different 44 | %%% kind of inputs. 45 | %%% If the Parent process terminates the Module:terminate/2 46 | %%% function is called. 47 | %%% 48 | %%% The user module should export: 49 | %%% 50 | %%% init(Args) 51 | %%% ==> {ok, State} 52 | %%% {ok, State, Timeout} 53 | %%% ignore 54 | %%% {stop, Reason} 55 | %%% 56 | %%% handle_call(Msg, {From, Tag}, State) 57 | %%% 58 | %%% ==> {reply, Reply, State} 59 | %%% {reply, Reply, State, Timeout} 60 | %%% {noreply, State} 61 | %%% {noreply, State, Timeout} 62 | %%% {stop, Reason, Reply, State} 63 | %%% Reason = normal | shutdown | Term terminate(State) is called 64 | %%% 65 | %%% handle_cast(Msg, State) 66 | %%% 67 | %%% ==> {noreply, State} 68 | %%% {noreply, State, Timeout} 69 | %%% {stop, Reason, State} 70 | %%% Reason = normal | shutdown | Term terminate(State) is called 71 | %%% 72 | %%% handle_info(Info, State) Info is e.g. {'EXIT', P, R}, {nodedown, N}, ... 73 | %%% 74 | %%% ==> {noreply, State} 75 | %%% {noreply, State, Timeout} 76 | %%% {stop, Reason, State} 77 | %%% Reason = normal | shutdown | Term, terminate(State) is called 78 | %%% 79 | %%% terminate(Reason, State) Let the user module clean up 80 | %%% always called when server terminates 81 | %%% 82 | %%% ==> ok 83 | %%% 84 | %%% 85 | %%% The work flow (of the server) can be described as follows: 86 | %%% 87 | %%% User module Generic 88 | %%% ----------- ------- 89 | %%% start -----> start 90 | %%% init <----- . 91 | %%% 92 | %%% loop 93 | %%% handle_call <----- . 94 | %%% -----> reply 95 | %%% 96 | %%% handle_cast <----- . 97 | %%% 98 | %%% handle_info <----- . 99 | %%% 100 | %%% terminate <----- . 101 | %%% 102 | %%% -----> reply 103 | %%% 104 | %%% 105 | %%% --------------------------------------------------- 106 | 107 | %% API 108 | -export([start/3, start/4, 109 | start_link/3, start_link/4, 110 | call/2, call/3, 111 | cast/2, reply/2, 112 | abcast/2, abcast/3, 113 | multi_call/2, multi_call/3, multi_call/4, 114 | enter_loop/3, enter_loop/4, enter_loop/5]). 115 | 116 | -export([behaviour_info/1]). 117 | 118 | %% System exports 119 | -export([system_continue/3, 120 | system_terminate/4, 121 | system_code_change/4, 122 | format_status/2]). 123 | 124 | %% Internal exports 125 | -export([init_it/6, print_event/3]). 126 | 127 | -import(error_logger, [format/2]). 128 | 129 | %%%========================================================================= 130 | %%% API 131 | %%%========================================================================= 132 | 133 | behaviour_info(callbacks) -> 134 | [{init,1},{handle_call,3},{handle_cast,2},{handle_info,2}, 135 | {terminate,2},{code_change,3}]; 136 | behaviour_info(_Other) -> 137 | undefined. 138 | 139 | %%% ----------------------------------------------------------------- 140 | %%% Starts a generic server. 141 | %%% start(Mod, Args, Options) 142 | %%% start(Name, Mod, Args, Options) 143 | %%% start_link(Mod, Args, Options) 144 | %%% start_link(Name, Mod, Args, Options) where: 145 | %%% Name ::= {local, atom()} | {global, atom()} 146 | %%% Mod ::= atom(), callback module implementing the 'real' server 147 | %%% Args ::= term(), init arguments (to Mod:init/1) 148 | %%% Options ::= [{timeout, Timeout} | {debug, [Flag]}] 149 | %%% Flag ::= trace | log | {logfile, File} | statistics | debug 150 | %%% (debug == log && statistics) 151 | %%% Returns: {ok, Pid} | 152 | %%% {error, {already_started, Pid}} | 153 | %%% {error, Reason} 154 | %%% ----------------------------------------------------------------- 155 | start(Mod, Args, Options) -> 156 | gen:start(?MODULE, nolink, Mod, Args, Options). 157 | 158 | start(Name, Mod, Args, Options) -> 159 | gen:start(?MODULE, nolink, Name, Mod, Args, Options). 160 | 161 | start_link(Mod, Args, Options) -> 162 | gen:start(?MODULE, link, Mod, Args, Options). 163 | 164 | start_link(Name, Mod, Args, Options) -> 165 | gen:start(?MODULE, link, Name, Mod, Args, Options). 166 | 167 | 168 | %% ----------------------------------------------------------------- 169 | %% Make a call to a generic server. 170 | %% If the server is located at another node, that node will 171 | %% be monitored. 172 | %% If the client is trapping exits and is linked server termination 173 | %% is handled here (? Shall we do that here (or rely on timeouts) ?). 174 | %% ----------------------------------------------------------------- 175 | call(Name, Request) -> 176 | case catch gen:call(Name, '$gen_call', Request) of 177 | {ok,Res} -> 178 | Res; 179 | {'EXIT',Reason} -> 180 | exit({Reason, {?MODULE, call, [Name, Request]}}) 181 | end. 182 | 183 | call(Name, Request, Timeout) -> 184 | case catch gen:call(Name, '$gen_call', Request, Timeout) of 185 | {ok,Res} -> 186 | Res; 187 | {'EXIT',Reason} -> 188 | exit({Reason, {?MODULE, call, [Name, Request, Timeout]}}) 189 | end. 190 | 191 | %% ----------------------------------------------------------------- 192 | %% Make a cast to a generic server. 193 | %% ----------------------------------------------------------------- 194 | cast({global,Name}, Request) -> 195 | catch global:send(Name, cast_msg(Request)), 196 | ok; 197 | cast({Name,Node}=Dest, Request) when is_atom(Name), is_atom(Node) -> 198 | do_cast(Dest, Request); 199 | cast(Dest, Request) when is_atom(Dest) -> 200 | do_cast(Dest, Request); 201 | cast(Dest, Request) when is_pid(Dest) -> 202 | do_cast(Dest, Request). 203 | 204 | do_cast(Dest, Request) -> 205 | do_send(Dest, cast_msg(Request)), 206 | ok. 207 | 208 | cast_msg(Request) -> {'$gen_cast',Request}. 209 | 210 | %% ----------------------------------------------------------------- 211 | %% Send a reply to the client. 212 | %% ----------------------------------------------------------------- 213 | reply({To, Tag}, Reply) -> 214 | catch To ! {Tag, Reply}. 215 | 216 | %% ----------------------------------------------------------------- 217 | %% Asyncronous broadcast, returns nothing, it's just send'n prey 218 | %%----------------------------------------------------------------- 219 | abcast(Name, Request) when is_atom(Name) -> 220 | do_abcast([node() | nodes()], Name, cast_msg(Request)). 221 | 222 | abcast(Nodes, Name, Request) when is_list(Nodes), is_atom(Name) -> 223 | do_abcast(Nodes, Name, cast_msg(Request)). 224 | 225 | do_abcast([Node|Nodes], Name, Msg) when is_atom(Node) -> 226 | do_send({Name,Node},Msg), 227 | do_abcast(Nodes, Name, Msg); 228 | do_abcast([], _,_) -> abcast. 229 | 230 | %%% ----------------------------------------------------------------- 231 | %%% Make a call to servers at several nodes. 232 | %%% Returns: {[Replies],[BadNodes]} 233 | %%% A Timeout can be given 234 | %%% 235 | %%% A middleman process is used in case late answers arrives after 236 | %%% the timeout. If they would be allowed to glog the callers message 237 | %%% queue, it would probably become confused. Late answers will 238 | %%% now arrive to the terminated middleman and so be discarded. 239 | %%% ----------------------------------------------------------------- 240 | multi_call(Name, Req) 241 | when is_atom(Name) -> 242 | do_multi_call([node() | nodes()], Name, Req, infinity). 243 | 244 | multi_call(Nodes, Name, Req) 245 | when is_list(Nodes), is_atom(Name) -> 246 | do_multi_call(Nodes, Name, Req, infinity). 247 | 248 | multi_call(Nodes, Name, Req, infinity) -> 249 | do_multi_call(Nodes, Name, Req, infinity); 250 | multi_call(Nodes, Name, Req, Timeout) 251 | when is_list(Nodes), is_atom(Name), is_integer(Timeout), Timeout >= 0 -> 252 | do_multi_call(Nodes, Name, Req, Timeout). 253 | 254 | 255 | %%----------------------------------------------------------------- 256 | %% enter_loop(Mod, Options, State, , ) ->_ 257 | %% 258 | %% Description: Makes an existing process into a gen_server. 259 | %% The calling process will enter the gen_server receive 260 | %% loop and become a gen_server process. 261 | %% The process *must* have been started using one of the 262 | %% start functions in proc_lib, see proc_lib(3). 263 | %% The user is responsible for any initialization of the 264 | %% process, including registering a name for it. 265 | %%----------------------------------------------------------------- 266 | enter_loop(Mod, Options, State) -> 267 | enter_loop(Mod, Options, State, self(), infinity). 268 | 269 | enter_loop(Mod, Options, State, ServerName = {_, _}) -> 270 | enter_loop(Mod, Options, State, ServerName, infinity); 271 | 272 | enter_loop(Mod, Options, State, Timeout) -> 273 | enter_loop(Mod, Options, State, self(), Timeout). 274 | 275 | enter_loop(Mod, Options, State, ServerName, Timeout) -> 276 | Name = get_proc_name(ServerName), 277 | Parent = get_parent(), 278 | Debug = debug_options(Name, Options), 279 | Queue = queue:new(), 280 | loop(Parent, Name, State, Mod, Timeout, Queue, Debug). 281 | 282 | %%%======================================================================== 283 | %%% Gen-callback functions 284 | %%%======================================================================== 285 | 286 | %%% --------------------------------------------------- 287 | %%% Initiate the new process. 288 | %%% Register the name using the Rfunc function 289 | %%% Calls the Mod:init/Args function. 290 | %%% Finally an acknowledge is sent to Parent and the main 291 | %%% loop is entered. 292 | %%% --------------------------------------------------- 293 | init_it(Starter, self, Name, Mod, Args, Options) -> 294 | init_it(Starter, self(), Name, Mod, Args, Options); 295 | init_it(Starter, Parent, Name, Mod, Args, Options) -> 296 | Debug = debug_options(Name, Options), 297 | Queue = queue:new(), 298 | case catch Mod:init(Args) of 299 | {ok, State} -> 300 | proc_lib:init_ack(Starter, {ok, self()}), 301 | loop(Parent, Name, State, Mod, infinity, Queue, Debug); 302 | {ok, State, Timeout} -> 303 | proc_lib:init_ack(Starter, {ok, self()}), 304 | loop(Parent, Name, State, Mod, Timeout, Queue, Debug); 305 | {stop, Reason} -> 306 | proc_lib:init_ack(Starter, {error, Reason}), 307 | exit(Reason); 308 | ignore -> 309 | proc_lib:init_ack(Starter, ignore), 310 | exit(normal); 311 | {'EXIT', Reason} -> 312 | proc_lib:init_ack(Starter, {error, Reason}), 313 | exit(Reason); 314 | Else -> 315 | Error = {bad_return_value, Else}, 316 | proc_lib:init_ack(Starter, {error, Error}), 317 | exit(Error) 318 | end. 319 | 320 | %%%======================================================================== 321 | %%% Internal functions 322 | %%%======================================================================== 323 | %%% --------------------------------------------------- 324 | %%% The MAIN loop. 325 | %%% --------------------------------------------------- 326 | loop(Parent, Name, State, Mod, Time, Queue, Debug) -> 327 | receive 328 | Input -> loop(Parent, Name, State, Mod, 329 | Time, queue:in(Input, Queue), Debug) 330 | after 0 -> 331 | case queue:out(Queue) of 332 | {{value, Msg}, Queue1} -> 333 | process_msg(Parent, Name, State, Mod, 334 | Time, Queue1, Debug, Msg); 335 | {empty, Queue1} -> 336 | receive 337 | Input -> 338 | loop(Parent, Name, State, Mod, 339 | Time, queue:in(Input, Queue1), Debug) 340 | after Time -> 341 | process_msg(Parent, Name, State, Mod, 342 | Time, Queue1, Debug, timeout) 343 | end 344 | end 345 | end. 346 | 347 | process_msg(Parent, Name, State, Mod, Time, Queue, Debug, Msg) -> 348 | case Msg of 349 | {system, From, Req} -> 350 | sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug, 351 | [Name, State, Mod, Time, Queue]); 352 | {'EXIT', Parent, Reason} -> 353 | terminate(Reason, Name, Msg, Mod, State, Debug); 354 | _Msg when Debug =:= [] -> 355 | handle_msg(Msg, Parent, Name, State, Mod, Time, Queue); 356 | _Msg -> 357 | Debug1 = sys:handle_debug(Debug, {?MODULE, print_event}, 358 | Name, {in, Msg}), 359 | handle_msg(Msg, Parent, Name, State, Mod, Time, Queue, Debug1) 360 | end. 361 | 362 | %%% --------------------------------------------------- 363 | %%% Send/recive functions 364 | %%% --------------------------------------------------- 365 | do_send(Dest, Msg) -> 366 | catch erlang:send(Dest, Msg). 367 | 368 | do_multi_call(Nodes, Name, Req, infinity) -> 369 | Tag = make_ref(), 370 | Monitors = send_nodes(Nodes, Name, Tag, Req), 371 | rec_nodes(Tag, Monitors, Name, undefined); 372 | do_multi_call(Nodes, Name, Req, Timeout) -> 373 | Tag = make_ref(), 374 | Caller = self(), 375 | Receiver = 376 | spawn( 377 | fun() -> 378 | %% Middleman process. Should be unsensitive to regular 379 | %% exit signals. The sychronization is needed in case 380 | %% the receiver would exit before the caller started 381 | %% the monitor. 382 | process_flag(trap_exit, true), 383 | Mref = erlang:monitor(process, Caller), 384 | receive 385 | {Caller,Tag} -> 386 | Monitors = send_nodes(Nodes, Name, Tag, Req), 387 | TimerId = erlang:start_timer(Timeout, self(), ok), 388 | Result = rec_nodes(Tag, Monitors, Name, TimerId), 389 | exit({self(),Tag,Result}); 390 | {'DOWN',Mref,_,_,_} -> 391 | %% Caller died before sending us the go-ahead. 392 | %% Give up silently. 393 | exit(normal) 394 | end 395 | end), 396 | Mref = erlang:monitor(process, Receiver), 397 | Receiver ! {self(),Tag}, 398 | receive 399 | {'DOWN',Mref,_,_,{Receiver,Tag,Result}} -> 400 | Result; 401 | {'DOWN',Mref,_,_,Reason} -> 402 | %% The middleman code failed. Or someone did 403 | %% exit(_, kill) on the middleman process => Reason==killed 404 | exit(Reason) 405 | end. 406 | 407 | send_nodes(Nodes, Name, Tag, Req) -> 408 | send_nodes(Nodes, Name, Tag, Req, []). 409 | 410 | send_nodes([Node|Tail], Name, Tag, Req, Monitors) 411 | when is_atom(Node) -> 412 | Monitor = start_monitor(Node, Name), 413 | %% Handle non-existing names in rec_nodes. 414 | catch {Name, Node} ! {'$gen_call', {self(), {Tag, Node}}, Req}, 415 | send_nodes(Tail, Name, Tag, Req, [Monitor | Monitors]); 416 | send_nodes([_Node|Tail], Name, Tag, Req, Monitors) -> 417 | %% Skip non-atom Node 418 | send_nodes(Tail, Name, Tag, Req, Monitors); 419 | send_nodes([], _Name, _Tag, _Req, Monitors) -> 420 | Monitors. 421 | 422 | %% Against old nodes: 423 | %% If no reply has been delivered within 2 secs. (per node) check that 424 | %% the server really exists and wait for ever for the answer. 425 | %% 426 | %% Against contemporary nodes: 427 | %% Wait for reply, server 'DOWN', or timeout from TimerId. 428 | 429 | rec_nodes(Tag, Nodes, Name, TimerId) -> 430 | rec_nodes(Tag, Nodes, Name, [], [], 2000, TimerId). 431 | 432 | rec_nodes(Tag, [{N,R}|Tail], Name, Badnodes, Replies, Time, TimerId ) -> 433 | receive 434 | {'DOWN', R, _, _, _} -> 435 | rec_nodes(Tag, Tail, Name, [N|Badnodes], Replies, Time, TimerId); 436 | {{Tag, N}, Reply} -> %% Tag is bound !!! 437 | unmonitor(R), 438 | rec_nodes(Tag, Tail, Name, Badnodes, 439 | [{N,Reply}|Replies], Time, TimerId); 440 | {timeout, TimerId, _} -> 441 | unmonitor(R), 442 | %% Collect all replies that already have arrived 443 | rec_nodes_rest(Tag, Tail, Name, [N|Badnodes], Replies) 444 | end; 445 | rec_nodes(Tag, [N|Tail], Name, Badnodes, Replies, Time, TimerId) -> 446 | %% R6 node 447 | receive 448 | {nodedown, N} -> 449 | monitor_node(N, false), 450 | rec_nodes(Tag, Tail, Name, [N|Badnodes], Replies, 2000, TimerId); 451 | {{Tag, N}, Reply} -> %% Tag is bound !!! 452 | receive {nodedown, N} -> ok after 0 -> ok end, 453 | monitor_node(N, false), 454 | rec_nodes(Tag, Tail, Name, Badnodes, 455 | [{N,Reply}|Replies], 2000, TimerId); 456 | {timeout, TimerId, _} -> 457 | receive {nodedown, N} -> ok after 0 -> ok end, 458 | monitor_node(N, false), 459 | %% Collect all replies that already have arrived 460 | rec_nodes_rest(Tag, Tail, Name, [N | Badnodes], Replies) 461 | after Time -> 462 | case rpc:call(N, erlang, whereis, [Name]) of 463 | Pid when is_pid(Pid) -> % It exists try again. 464 | rec_nodes(Tag, [N|Tail], Name, Badnodes, 465 | Replies, infinity, TimerId); 466 | _ -> % badnode 467 | receive {nodedown, N} -> ok after 0 -> ok end, 468 | monitor_node(N, false), 469 | rec_nodes(Tag, Tail, Name, [N|Badnodes], 470 | Replies, 2000, TimerId) 471 | end 472 | end; 473 | rec_nodes(_, [], _, Badnodes, Replies, _, TimerId) -> 474 | case catch erlang:cancel_timer(TimerId) of 475 | false -> % It has already sent it's message 476 | receive 477 | {timeout, TimerId, _} -> ok 478 | after 0 -> 479 | ok 480 | end; 481 | _ -> % Timer was cancelled, or TimerId was 'undefined' 482 | ok 483 | end, 484 | {Replies, Badnodes}. 485 | 486 | %% Collect all replies that already have arrived 487 | rec_nodes_rest(Tag, [{N,R}|Tail], Name, Badnodes, Replies) -> 488 | receive 489 | {'DOWN', R, _, _, _} -> 490 | rec_nodes_rest(Tag, Tail, Name, [N|Badnodes], Replies); 491 | {{Tag, N}, Reply} -> %% Tag is bound !!! 492 | unmonitor(R), 493 | rec_nodes_rest(Tag, Tail, Name, Badnodes, [{N,Reply}|Replies]) 494 | after 0 -> 495 | unmonitor(R), 496 | rec_nodes_rest(Tag, Tail, Name, [N|Badnodes], Replies) 497 | end; 498 | rec_nodes_rest(Tag, [N|Tail], Name, Badnodes, Replies) -> 499 | %% R6 node 500 | receive 501 | {nodedown, N} -> 502 | monitor_node(N, false), 503 | rec_nodes_rest(Tag, Tail, Name, [N|Badnodes], Replies); 504 | {{Tag, N}, Reply} -> %% Tag is bound !!! 505 | receive {nodedown, N} -> ok after 0 -> ok end, 506 | monitor_node(N, false), 507 | rec_nodes_rest(Tag, Tail, Name, Badnodes, [{N,Reply}|Replies]) 508 | after 0 -> 509 | receive {nodedown, N} -> ok after 0 -> ok end, 510 | monitor_node(N, false), 511 | rec_nodes_rest(Tag, Tail, Name, [N|Badnodes], Replies) 512 | end; 513 | rec_nodes_rest(_Tag, [], _Name, Badnodes, Replies) -> 514 | {Replies, Badnodes}. 515 | 516 | 517 | %%% --------------------------------------------------- 518 | %%% Monitor functions 519 | %%% --------------------------------------------------- 520 | 521 | start_monitor(Node, Name) when is_atom(Node), is_atom(Name) -> 522 | if node() =:= nonode@nohost, Node =/= nonode@nohost -> 523 | Ref = make_ref(), 524 | self() ! {'DOWN', Ref, process, {Name, Node}, noconnection}, 525 | {Node, Ref}; 526 | true -> 527 | case catch erlang:monitor(process, {Name, Node}) of 528 | {'EXIT', _} -> 529 | %% Remote node is R6 530 | monitor_node(Node, true), 531 | Node; 532 | Ref when is_reference(Ref) -> 533 | {Node, Ref} 534 | end 535 | end. 536 | 537 | %% Cancels a monitor started with Ref=erlang:monitor(_, _). 538 | unmonitor(Ref) when is_reference(Ref) -> 539 | erlang:demonitor(Ref), 540 | receive 541 | {'DOWN', Ref, _, _, _} -> 542 | true 543 | after 0 -> 544 | true 545 | end. 546 | 547 | %%% --------------------------------------------------- 548 | %%% Message handling functions 549 | %%% --------------------------------------------------- 550 | 551 | dispatch({'$gen_cast', Msg}, Mod, State) -> 552 | Mod:handle_cast(Msg, State); 553 | dispatch(Info, Mod, State) -> 554 | Mod:handle_info(Info, State). 555 | 556 | handle_msg({'$gen_call', From, Msg}, 557 | Parent, Name, State, Mod, _Time, Queue) -> 558 | case catch Mod:handle_call(Msg, From, State) of 559 | {reply, Reply, NState} -> 560 | reply(From, Reply), 561 | loop(Parent, Name, NState, Mod, infinity, Queue, []); 562 | {reply, Reply, NState, Time1} -> 563 | reply(From, Reply), 564 | loop(Parent, Name, NState, Mod, Time1, Queue, []); 565 | {noreply, NState} -> 566 | loop(Parent, Name, NState, Mod, infinity, Queue, []); 567 | {noreply, NState, Time1} -> 568 | loop(Parent, Name, NState, Mod, Time1, Queue, []); 569 | {stop, Reason, Reply, NState} -> 570 | {'EXIT', R} = 571 | (catch terminate(Reason, Name, Msg, Mod, NState, [])), 572 | reply(From, Reply), 573 | exit(R); 574 | Other -> handle_common_reply(Other, 575 | Parent, Name, Msg, Mod, State, Queue) 576 | end; 577 | handle_msg(Msg, 578 | Parent, Name, State, Mod, _Time, Queue) -> 579 | Reply = (catch dispatch(Msg, Mod, State)), 580 | handle_common_reply(Reply, Parent, Name, Msg, Mod, State, Queue). 581 | 582 | handle_msg({'$gen_call', From, Msg}, 583 | Parent, Name, State, Mod, _Time, Queue, Debug) -> 584 | case catch Mod:handle_call(Msg, From, State) of 585 | {reply, Reply, NState} -> 586 | Debug1 = reply(Name, From, Reply, NState, Debug), 587 | loop(Parent, Name, NState, Mod, infinity, Queue, Debug1); 588 | {reply, Reply, NState, Time1} -> 589 | Debug1 = reply(Name, From, Reply, NState, Debug), 590 | loop(Parent, Name, NState, Mod, Time1, Queue, Debug1); 591 | {noreply, NState} -> 592 | Debug1 = sys:handle_debug(Debug, {?MODULE, print_event}, Name, 593 | {noreply, NState}), 594 | loop(Parent, Name, NState, Mod, infinity, Queue, Debug1); 595 | {noreply, NState, Time1} -> 596 | Debug1 = sys:handle_debug(Debug, {?MODULE, print_event}, Name, 597 | {noreply, NState}), 598 | loop(Parent, Name, NState, Mod, Time1, Queue, Debug1); 599 | {stop, Reason, Reply, NState} -> 600 | {'EXIT', R} = 601 | (catch terminate(Reason, Name, Msg, Mod, NState, Debug)), 602 | reply(Name, From, Reply, NState, Debug), 603 | exit(R); 604 | Other -> 605 | handle_common_reply(Other, 606 | Parent, Name, Msg, Mod, State, Queue, Debug) 607 | end; 608 | handle_msg(Msg, 609 | Parent, Name, State, Mod, _Time, Queue, Debug) -> 610 | Reply = (catch dispatch(Msg, Mod, State)), 611 | handle_common_reply(Reply, 612 | Parent, Name, Msg, Mod, State, Queue, Debug). 613 | 614 | handle_common_reply(Reply, Parent, Name, Msg, Mod, State, Queue) -> 615 | case Reply of 616 | {noreply, NState} -> 617 | loop(Parent, Name, NState, Mod, infinity, Queue, []); 618 | {noreply, NState, Time1} -> 619 | loop(Parent, Name, NState, Mod, Time1, Queue, []); 620 | {stop, Reason, NState} -> 621 | terminate(Reason, Name, Msg, Mod, NState, []); 622 | {'EXIT', What} -> 623 | terminate(What, Name, Msg, Mod, State, []); 624 | _ -> 625 | terminate({bad_return_value, Reply}, Name, Msg, Mod, State, []) 626 | end. 627 | 628 | handle_common_reply(Reply, Parent, Name, Msg, Mod, State, Queue, Debug) -> 629 | case Reply of 630 | {noreply, NState} -> 631 | Debug1 = sys:handle_debug(Debug, {?MODULE, print_event}, Name, 632 | {noreply, NState}), 633 | loop(Parent, Name, NState, Mod, infinity, Queue, Debug1); 634 | {noreply, NState, Time1} -> 635 | Debug1 = sys:handle_debug(Debug, {?MODULE, print_event}, Name, 636 | {noreply, NState}), 637 | loop(Parent, Name, NState, Mod, Time1, Queue, Debug1); 638 | {stop, Reason, NState} -> 639 | terminate(Reason, Name, Msg, Mod, NState, Debug); 640 | {'EXIT', What} -> 641 | terminate(What, Name, Msg, Mod, State, Debug); 642 | _ -> 643 | terminate({bad_return_value, Reply}, Name, Msg, Mod, State, Debug) 644 | end. 645 | 646 | reply(Name, {To, Tag}, Reply, State, Debug) -> 647 | reply({To, Tag}, Reply), 648 | sys:handle_debug(Debug, {?MODULE, print_event}, Name, 649 | {out, Reply, To, State} ). 650 | 651 | 652 | %%----------------------------------------------------------------- 653 | %% Callback functions for system messages handling. 654 | %%----------------------------------------------------------------- 655 | system_continue(Parent, Debug, [Name, State, Mod, Time, Queue]) -> 656 | loop(Parent, Name, State, Mod, Time, Queue, Debug). 657 | 658 | system_terminate(Reason, _Parent, Debug, [Name, State, Mod, _Time, _Queue]) -> 659 | terminate(Reason, Name, [], Mod, State, Debug). 660 | 661 | system_code_change([Name, State, Mod, Time, Queue], _Module, OldVsn, Extra) -> 662 | case catch Mod:code_change(OldVsn, State, Extra) of 663 | {ok, NewState} -> {ok, [Name, NewState, Mod, Time, Queue]}; 664 | Else -> Else 665 | end. 666 | 667 | %%----------------------------------------------------------------- 668 | %% Format debug messages. Print them as the call-back module sees 669 | %% them, not as the real erlang messages. Use trace for that. 670 | %%----------------------------------------------------------------- 671 | print_event(Dev, {in, Msg}, Name) -> 672 | case Msg of 673 | {'$gen_call', {From, _Tag}, Call} -> 674 | io:format(Dev, "*DBG* ~p got call ~p from ~w~n", 675 | [Name, Call, From]); 676 | {'$gen_cast', Cast} -> 677 | io:format(Dev, "*DBG* ~p got cast ~p~n", 678 | [Name, Cast]); 679 | _ -> 680 | io:format(Dev, "*DBG* ~p got ~p~n", [Name, Msg]) 681 | end; 682 | print_event(Dev, {out, Msg, To, State}, Name) -> 683 | io:format(Dev, "*DBG* ~p sent ~p to ~w, new state ~w~n", 684 | [Name, Msg, To, State]); 685 | print_event(Dev, {noreply, State}, Name) -> 686 | io:format(Dev, "*DBG* ~p new state ~w~n", [Name, State]); 687 | print_event(Dev, Event, Name) -> 688 | io:format(Dev, "*DBG* ~p dbg ~p~n", [Name, Event]). 689 | 690 | 691 | %%% --------------------------------------------------- 692 | %%% Terminate the server. 693 | %%% --------------------------------------------------- 694 | 695 | terminate(Reason, Name, Msg, Mod, State, Debug) -> 696 | case catch Mod:terminate(Reason, State) of 697 | {'EXIT', R} -> 698 | error_info(R, Name, Msg, State, Debug), 699 | exit(R); 700 | _ -> 701 | case Reason of 702 | normal -> 703 | exit(normal); 704 | shutdown -> 705 | exit(shutdown); 706 | _ -> 707 | error_info(Reason, Name, Msg, State, Debug), 708 | exit(Reason) 709 | end 710 | end. 711 | 712 | error_info(_Reason, application_controller, _Msg, _State, _Debug) -> 713 | %% OTP-5811 Don't send an error report if it's the system process 714 | %% application_controller which is terminating - let init take care 715 | %% of it instead 716 | ok; 717 | error_info(Reason, Name, Msg, State, Debug) -> 718 | Reason1 = 719 | case Reason of 720 | {undef,[{M,F,A}|MFAs]} -> 721 | case code:is_loaded(M) of 722 | false -> 723 | {'module could not be loaded',[{M,F,A}|MFAs]}; 724 | _ -> 725 | case erlang:function_exported(M, F, length(A)) of 726 | true -> 727 | Reason; 728 | false -> 729 | {'function not exported',[{M,F,A}|MFAs]} 730 | end 731 | end; 732 | _ -> 733 | Reason 734 | end, 735 | format("** Generic server ~p terminating \n" 736 | "** Last message in was ~p~n" 737 | "** When Server state == ~p~n" 738 | "** Reason for termination == ~n** ~p~n", 739 | [Name, Msg, State, Reason1]), 740 | sys:print_log(Debug), 741 | ok. 742 | 743 | %%% --------------------------------------------------- 744 | %%% Misc. functions. 745 | %%% --------------------------------------------------- 746 | 747 | opt(Op, [{Op, Value}|_]) -> 748 | {ok, Value}; 749 | opt(Op, [_|Options]) -> 750 | opt(Op, Options); 751 | opt(_, []) -> 752 | false. 753 | 754 | debug_options(Name, Opts) -> 755 | case opt(debug, Opts) of 756 | {ok, Options} -> dbg_options(Name, Options); 757 | _ -> dbg_options(Name, []) 758 | end. 759 | 760 | dbg_options(Name, []) -> 761 | Opts = 762 | case init:get_argument(generic_debug) of 763 | error -> 764 | []; 765 | _ -> 766 | [log, statistics] 767 | end, 768 | dbg_opts(Name, Opts); 769 | dbg_options(Name, Opts) -> 770 | dbg_opts(Name, Opts). 771 | 772 | dbg_opts(Name, Opts) -> 773 | case catch sys:debug_options(Opts) of 774 | {'EXIT',_} -> 775 | format("~p: ignoring erroneous debug options - ~p~n", 776 | [Name, Opts]), 777 | []; 778 | Dbg -> 779 | Dbg 780 | end. 781 | 782 | get_proc_name(Pid) when is_pid(Pid) -> 783 | Pid; 784 | get_proc_name({local, Name}) -> 785 | case process_info(self(), registered_name) of 786 | {registered_name, Name} -> 787 | Name; 788 | {registered_name, _Name} -> 789 | exit(process_not_registered); 790 | [] -> 791 | exit(process_not_registered) 792 | end; 793 | get_proc_name({global, Name}) -> 794 | case global:safe_whereis_name(Name) of 795 | undefined -> 796 | exit(process_not_registered_globally); 797 | Pid when Pid =:= self() -> 798 | Name; 799 | _Pid -> 800 | exit(process_not_registered_globally) 801 | end. 802 | 803 | get_parent() -> 804 | case get('$ancestors') of 805 | [Parent | _] when is_pid(Parent)-> 806 | Parent; 807 | [Parent | _] when is_atom(Parent)-> 808 | name_to_pid(Parent); 809 | _ -> 810 | exit(process_was_not_started_by_proc_lib) 811 | end. 812 | 813 | name_to_pid(Name) -> 814 | case whereis(Name) of 815 | undefined -> 816 | case global:safe_whereis_name(Name) of 817 | undefined -> 818 | exit(could_not_find_registerd_name); 819 | Pid -> 820 | Pid 821 | end; 822 | Pid -> 823 | Pid 824 | end. 825 | 826 | %%----------------------------------------------------------------- 827 | %% Status information 828 | %%----------------------------------------------------------------- 829 | format_status(Opt, StatusData) -> 830 | [PDict, SysState, Parent, Debug, [Name, State, Mod, _Time, Queue]] = 831 | StatusData, 832 | NameTag = if is_pid(Name) -> 833 | pid_to_list(Name); 834 | is_atom(Name) -> 835 | Name 836 | end, 837 | Header = lists:concat(["Status for generic server ", NameTag]), 838 | Log = sys:get_debug(log, Debug, []), 839 | Specfic = 840 | case erlang:function_exported(Mod, format_status, 2) of 841 | true -> 842 | case catch Mod:format_status(Opt, [PDict, State]) of 843 | {'EXIT', _} -> [{data, [{"State", State}]}]; 844 | Else -> Else 845 | end; 846 | _ -> 847 | [{data, [{"State", State}]}] 848 | end, 849 | [{header, Header}, 850 | {data, [{"Status", SysState}, 851 | {"Parent", Parent}, 852 | {"Logged events", Log}, 853 | {"Queued messages", queue:to_list(Queue)}]} | 854 | Specfic]. -------------------------------------------------------------------------------- /test/a_erldis_multiexec_tests.erl: -------------------------------------------------------------------------------- 1 | -module(a_erldis_multiexec_tests). 2 | 3 | -include_lib("eunit/include/eunit.hrl"). 4 | 5 | me_test()-> 6 | application:load(erldis), 7 | {ok, Client} = erldis_client:connect(), 8 | erldis:flushdb(Client), 9 | Fun = fun(C)-> 10 | erldis:set(C, <<"toto">>, <<"tata">>), 11 | erldis:set(C, <<"toto2">>, <<"tata2">>), 12 | erldis:get(C, <<"toto">>) 13 | end, 14 | ?assertEqual([ok, ok, <<"tata">>],erldis:exec(Client, Fun)), 15 | Fun2 = fun(C)-> 16 | erldis:sadd(C, <<"foo">>, <<"bar">>), 17 | erldis:srem(C, <<"foo">>, <<"bar">>), 18 | erldis:set(C, <<"foo3">>, <<"bar3">>) 19 | end, 20 | ?assertEqual([true, true, ok],erldis:exec(Client, Fun2)). 21 | -------------------------------------------------------------------------------- /test/erldis_dict_tests.erl: -------------------------------------------------------------------------------- 1 | -module(erldis_dict_tests). 2 | 3 | -include_lib("eunit/include/eunit.hrl"). 4 | 5 | dict_test() -> 6 | % setup 7 | application:load(erldis), 8 | {ok, Client} = erldis_client:connect(), 9 | ?assertEqual(erldis:flushdb(Client), ok), 10 | % empty dict 11 | ?assertEqual(0, erldis_dict:size(Client)), 12 | ?assertEqual(undefined, erldis_dict:fetch(<<"foo">>, Client)), 13 | ?assertEqual(error, erldis_dict:find(<<"foo">>, Client)), 14 | ?assertEqual(false, erldis_dict:is_key(<<"foo">>, Client)), 15 | % add element 16 | erldis_dict:store(<<"foo">>, <<"bar">>, Client), 17 | ?assertEqual(<<"bar">>, erldis_dict:fetch(<<"foo">>, Client)), 18 | ?assertEqual({ok, <<"bar">>}, erldis_dict:find(<<"foo">>, Client)), 19 | ?assertEqual(1, erldis_dict:size(Client)), 20 | ?assertEqual(true, erldis_dict:is_key(<<"foo">>, Client)), 21 | % del element 22 | erldis_dict:erase(<<"foo">>, Client), 23 | ?assertEqual(error, erldis_dict:find(<<"foo">>, Client)), 24 | ?assertEqual(0, erldis_dict:size(Client)), 25 | ?assertEqual(false, erldis_dict:is_key(<<"foo">>, Client)), 26 | % update counter 27 | ?assertEqual(1, erldis_dict:update_counter(<<"count">>, Client)), 28 | ?assertEqual(3, erldis_dict:update_counter(<<"count">>, 2, Client)), 29 | erldis_dict:erase(<<"count">>, Client), 30 | erldis_client:stop(Client). -------------------------------------------------------------------------------- /test/erldis_list_compatibility_tests.erl: -------------------------------------------------------------------------------- 1 | -module(erldis_list_compatibility_tests). 2 | 3 | -include_lib("eunit/include/eunit.hrl"). 4 | 5 | getset_test() -> 6 | Client = setup(), 7 | ?assertEqual(erldis:exists(Client, <<"foo">>), false), 8 | ?assertEqual(erldis:exists(Client, "foo"), false), 9 | ?assertEqual(erldis:get(Client, <<"foo">>), nil), 10 | ?assertEqual(erldis:get(Client, "foo"), nil), 11 | ?assertEqual(erldis:set(Client, <<"foo">>, <<"bar">>), ok), 12 | ?assertEqual(erldis:get(Client, <<"foo">>), <<"bar">>), 13 | ?assertEqual(erldis:get(Client, "foo"), <<"bar">>), 14 | ?assertEqual(erldis:exists(Client, <<"foo">>), true), 15 | ?assertEqual(erldis:exists(Client, "foo"), true), 16 | ?assertEqual(erldis:del(Client, "foo"), true), 17 | ?assertEqual(erldis:exists(Client, <<"foo">>), false), 18 | ?assertEqual(erldis:exists(Client, "foo"), false), 19 | ?assertEqual(erldis:set(Client, "fool", "baz"), ok), 20 | ?assertEqual(erldis:get(Client, "fool"), <<"baz">>), 21 | ?assertEqual(erldis:get(Client, <<"fool">>), <<"baz">>), 22 | ?assertEqual(erldis:exists(Client, <<"fool">>), true), 23 | ?assertEqual(erldis:exists(Client, "fool"), true), 24 | ?assertEqual(erldis:del(Client, <<"fool">>), true), 25 | ?assertEqual(erldis:exists(Client, <<"fool">>), false), 26 | ?assertEqual(erldis:exists(Client, "fool"), false). 27 | 28 | incrdecr_test() -> 29 | Client = setup(), 30 | ?assertEqual(erldis:incr(Client, <<"foo">>), 1), 31 | ?assertEqual(erldis:incr(Client, "foo"), 2), 32 | ?assertEqual(erldis:incrby(Client, <<"foo">>, 2), 4), 33 | ?assertEqual(erldis:incrby(Client, "foo", 3), 7), 34 | ?assertEqual(erldis:decr(Client, <<"foo">>), 6), 35 | ?assertEqual(erldis:decr(Client, "foo"), 5), 36 | ?assertEqual(erldis:decrby(Client, <<"foo">>, 2), 3), 37 | ?assertEqual(erldis:decrby(Client, "foo", 3), 0). 38 | 39 | list_test() -> 40 | Client = setup(), 41 | ?assertEqual(erldis:exists(Client, "foo"), false), 42 | ?assertEqual(erldis:lpush(Client, "foo", "a"), 1), 43 | ?assertEqual(erldis:rpush(Client, "foo", "b"), 2), 44 | ?assertEqual(erldis:lindex(Client, "foo", 0), <<"a">>), 45 | ?assertEqual(erldis:lindex(Client, "foo", 1), <<"b">>), 46 | ?assertEqual(erldis:llen(Client, "foo"), 2), 47 | ?assertEqual(erldis_list:is_list("foo", Client), true). 48 | 49 | set_test() -> 50 | Client = setup(), 51 | ?assertEqual(erldis:exists(Client, "foo"), false), 52 | ?assertEqual(erldis:sadd(Client, "foo", "bar"), true), 53 | ?assertEqual(erldis:scard(Client, "foo"), 1), 54 | ?assertEqual(erldis:smembers(Client, "foo"), [<<"bar">>]), 55 | ?assertEqual(erldis:sismember(Client, "foo", "bar"), true), 56 | ?assertEqual(erldis_sets:is_set(Client, "foo"), true). 57 | 58 | json_test() -> 59 | % requires yaws json module 60 | Arr = {array, ["a", "b", "c"]}, 61 | case code:which(json) of 62 | non_existing -> ok; 63 | _ -> Json = json:encode(Arr), 64 | Client = setup(), 65 | ?assertEqual(erldis:set(Client, "json", Json), ok), 66 | GetJson = erldis:get(Client, "json"), 67 | ?assertEqual(GetJson, list_to_binary(Json)), 68 | {ok, Arr2} = json:decode_string(binary_to_list(GetJson)), 69 | ?assertEqual(Arr, Arr2) 70 | end. 71 | 72 | setup() -> 73 | % setup 74 | application:load(erldis), 75 | {ok, Client} = erldis_client:connect(), 76 | ?assertEqual(erldis:flushdb(Client), ok), 77 | Client. 78 | -------------------------------------------------------------------------------- /test/erldis_list_tests.erl: -------------------------------------------------------------------------------- 1 | -module(erldis_list_tests). 2 | 3 | -include_lib("eunit/include/eunit.hrl"). 4 | 5 | queue_test() -> 6 | Client = setup(), 7 | % queue api 8 | ?assertEqual(true, erldis_list:is_empty(<<"foo">>, Client)), 9 | ?assertEqual(0, erldis_list:len(<<"foo">>, Client)), 10 | ?assertEqual(empty, erldis_list:out(<<"foo">>, Client)), 11 | erldis_list:in(<<"a">>, <<"foo">>, Client), 12 | ?assertEqual(false, erldis_list:is_empty(<<"foo">>, Client)), 13 | erldis_list:in(<<"b">>, <<"foo">>, Client), 14 | ?assertEqual(2, erldis_list:len(<<"foo">>, Client)), 15 | ?assertEqual({value, <<"a">>}, erldis_list:out(<<"foo">>, Client)), 16 | ?assertEqual(1, erldis_list:len(<<"foo">>, Client)), 17 | erldis_list:in_r(<<"x">>, <<"foo">>, Client), 18 | ?assertEqual({value, <<"b">>}, erldis_list:out_r(<<"foo">>, Client)), 19 | ?assertEqual(false, erldis_list:is_empty(<<"foo">>, Client)), 20 | ?assertEqual({value, <<"x">>}, erldis_list:out(<<"foo">>, Client)), 21 | ?assertEqual(0, erldis_list:len(<<"foo">>, Client)), 22 | ?assertEqual(empty, erldis_list:out(<<"foo">>, Client)), 23 | erldis_client:stop(Client). 24 | 25 | extended_queue_test() -> 26 | Client = setup(), 27 | ?assertEqual(empty, erldis_list:get(<<"foo">>, Client)), 28 | ?assertEqual(empty, erldis_list:get_r(<<"foo">>, Client)), 29 | ?assertEqual(empty, erldis_list:peek(<<"foo">>, Client)), 30 | ?assertEqual(empty, erldis_list:peek_r(<<"foo">>, Client)), 31 | erldis_list:in(<<"a">>, <<"foo">>, Client), 32 | erldis_list:in(<<"b">>, <<"foo">>, Client), 33 | ?assertEqual(<<"a">>, erldis_list:get(<<"foo">>, Client)), 34 | ?assertEqual(<<"b">>, erldis_list:get_r(<<"foo">>, Client)), 35 | ?assertEqual(2, erldis_list:len(<<"foo">>, Client)), 36 | ?assertEqual({value, <<"a">>}, erldis_list:peek(<<"foo">>, Client)), 37 | ?assertEqual({value, <<"b">>}, erldis_list:peek_r(<<"foo">>, Client)), 38 | erldis_list:drop(<<"foo">>, Client), 39 | erldis_list:drop_r(<<"foo">>, Client), 40 | ?assertEqual(0, erldis_list:len(<<"foo">>, Client)), 41 | erldis_client:stop(Client). 42 | 43 | array_test() -> 44 | Client = setup(), 45 | erldis_list:in(<<"a">>, <<"foo">>, Client), 46 | erldis_list:in(<<"b">>, <<"foo">>, Client), 47 | ?assertEqual(<<"b">>, erldis_list:get(1, <<"foo">>, Client)), 48 | erldis_list:set(1, <<"x">>, <<"foo">>, Client), 49 | ?assertEqual(<<"x">>, erldis_list:get(1, <<"foo">>, Client)), 50 | ?assertEqual(2, erldis_list:size(<<"foo">>, Client)), 51 | ?assertEqual({value, <<"a">>}, erldis_list:out(<<"foo">>, Client)), 52 | ?assertEqual({value, <<"x">>}, erldis_list:out(<<"foo">>, Client)), 53 | erldis_client:stop(Client). 54 | 55 | lists_test() -> 56 | Client = setup(), 57 | ?assertEqual(false, erldis_list:is_list(<<"foo">>, Client)), 58 | ?assertEqual([], erldis_list:sublist(<<"foo">>, Client, 1)), 59 | erldis_list:in(<<"a">>, <<"foo">>, Client), 60 | erldis_list:in(<<"b">>, <<"foo">>, Client), 61 | erldis_list:in(<<"c">>, <<"foo">>, Client), 62 | erldis_list:in(<<"b">>, <<"foo">>, Client), 63 | ?assertEqual([<<"b">>, <<"c">>], erldis_list:sublist(<<"foo">>, Client, 2, 2)), 64 | ?assertEqual(<<"b">>, erldis_list:nth(1, <<"foo">>, Client)), 65 | erldis_list:delete(<<"b">>, <<"foo">>, Client), 66 | ?assertEqual(<<"c">>, erldis_list:nth(1, <<"foo">>, Client)), 67 | ?assertEqual(3, erldis_list:len(<<"foo">>, Client)), 68 | ?assertEqual([<<"c">>, <<"b">>], erldis_list:sublist(<<"foo">>, Client, 2, 2)), 69 | erldis_list:drop(<<"foo">>, Client), 70 | erldis_list:drop(<<"foo">>, Client), 71 | erldis_list:drop(<<"foo">>, Client), 72 | erldis_client:stop(Client). 73 | % this last call always produces a timeout error 74 | %?assertEqual([], erldis_list:sublist(<<"foo">>, Client, 3)). 75 | % TODO: test negative sublist start index 76 | 77 | blocking_queue_test() -> 78 | Client = setup(), 79 | 80 | erldis:rpush(Client, <<"a">>, <<"value">>), 81 | ?assertEqual([<<"a">>, <<"value">>], erldis:blpop(Client, [<<"a">>, <<"b">>])), 82 | erldis:rpush(Client, <<"b">>, <<"value">>), 83 | ?assertEqual([<<"b">>, <<"value">>], erldis:blpop(Client, [<<"a">>, <<"b">>])), 84 | erldis:rpush(Client, <<"a">>, <<"first">>), 85 | erldis:rpush(Client, <<"a">>, <<"second">>), 86 | ?assertEqual([<<"a">>, <<"first">>], erldis:blpop(Client, [<<"a">>, <<"b">>])), 87 | ?assertEqual([<<"a">>, <<"second">>], erldis:blpop(Client, [<<"a">>, <<"b">>])), 88 | 89 | spawn_link(fun blocking_queue_sender/0), 90 | ?assertEqual([<<"a">>, <<1>>], erldis:blpop(Client, [<<"a">>, <<"b">>])), 91 | ?assertEqual([<<"b">>, <<1>>], erldis:blpop(Client, [<<"a">>, <<"b">>])), 92 | ?assertEqual([<<"a">>, <<2>>], erldis:blpop(Client, [<<"a">>, <<"b">>])), 93 | %?assertEqual([], erldis:blpop(Client, [<<"a">>, <<"b">>])), 94 | ?assertEqual([<<"a">>, <<3>>], erldis:blpop(Client, [<<"a">>, <<"b">>])), 95 | 96 | erldis_client:stop(Client). 97 | 98 | blocking_queue_sender() -> 99 | Client = setup(), 100 | erldis:rpush(Client, <<"a">>, <<1>>), 101 | timer:sleep(100), 102 | erldis:rpush(Client, <<"b">>, <<1>>), 103 | timer:sleep(100), 104 | erldis:rpush(Client, <<"a">>, <<2>>), 105 | timer:sleep(3000), 106 | erldis:rpush(Client, <<"a">>, <<3>>), 107 | erldis_client:stop(Client). 108 | 109 | foreach_test() -> 110 | Client = setup(), 111 | ?assertEqual(0, erldis_list:len(<<"foo">>, Client)), 112 | L = [<<"a">>, <<"b">>, <<"c">>], 113 | erldis_list:from_list(L, <<"foo">>, Client), 114 | ?assertEqual(length(L), erldis_list:len(<<"foo">>, Client)), 115 | put(n, 1), 116 | 117 | F = fun(Item) -> 118 | N = get(n), 119 | ?assertEqual(lists:nth(N, L), Item), 120 | put(n, N+1) 121 | end, 122 | 123 | erldis_list:foreach(F, <<"foo">>, Client), 124 | erldis_client:stop(Client). 125 | 126 | merge_test() -> 127 | Client = setup(), 128 | ?assertEqual(0, erldis_list:len(<<"foo">>, Client)), 129 | L1 = [<<"a">>, <<"c">>, <<"e">>], 130 | erldis_list:from_list(L1, <<"foo">>, Client), 131 | ?assertEqual(length(L1), erldis_list:len(<<"foo">>, Client)), 132 | L2 = [<<"b">>, <<"d">>, <<"f">>], 133 | F = fun(A, B) -> A =< B end, 134 | erldis_list:merge(F, L2, <<"foo">>, Client), 135 | Merged = lists:merge(F, L1, L2), 136 | ?assertEqual(Merged, lists:merge(L1, L2)), 137 | ?assertEqual(length(Merged), erldis_list:len(<<"foo">>, Client)), 138 | ?assertEqual(Merged, erldis_list:to_list(<<"foo">>, Client)), 139 | 140 | L3 = [<<"a">>, <<"c">>, <<"f">>, <<"g">>], 141 | erldis_list:umerge(F, L3, <<"foo">>, Client), 142 | Merged2 = lists:umerge(F, Merged, L3), 143 | ?assertEqual(Merged2, lists:umerge(Merged, L3)), 144 | ?assertEqual(length(Merged2), erldis_list:len(<<"foo">>, Client)), 145 | ?assertEqual(Merged2, erldis_list:to_list(<<"foo">>, Client)), 146 | 147 | erldis_client:stop(Client). 148 | 149 | umerge_test() -> 150 | Client = setup(), 151 | Key = <<"foo">>, 152 | F = fun(A, B) -> A =< B end, 153 | ?assertEqual(0, erldis_list:len(Key, Client)), 154 | L1 = [<<"a">>, <<"c">>, <<"e">>], 155 | erldis_list:umerge(F, L1, Key, Client), 156 | L1 = erldis_list:to_list(Key, Client), 157 | erldis_list:umerge(F, L1, Key, Client), 158 | L1 = erldis_list:to_list(Key, Client), 159 | erldis_list:umerge(F, [<<"a">>], Key, Client), 160 | L1 = erldis_list:to_list(Key, Client), 161 | erldis_client:stop(Client). 162 | 163 | common_test() -> 164 | Client = setup(), 165 | ?assertEqual(0, erldis_list:len(<<"foo">>, Client)), 166 | L = [<<"a">>, <<"b">>, <<"c">>], 167 | erldis_list:from_list(L, <<"foo">>, Client), 168 | ?assertEqual(length(L), erldis_list:len(<<"foo">>, Client)), 169 | % to_list uses foldr 170 | ?assertEqual(L, erldis_list:to_list(<<"foo">>, Client)), 171 | % reverse uses foldl 172 | ?assertEqual(lists:reverse(L), erldis_list:reverse(<<"foo">>, Client)), 173 | % from_list overwrites current list if it exists 174 | L2 = [<<"d">> | L], 175 | erldis_list:from_list(L2, <<"foo">>, Client), 176 | ?assertEqual(length(L2), erldis_list:len(<<"foo">>, Client)), 177 | ?assertEqual(L2, erldis_list:to_list(<<"foo">>, Client)), 178 | erldis_client:stop(Client). 179 | 180 | extra_queue_test() -> 181 | Client = setup(), 182 | L = [<<"a">>, <<"b">>, <<"c">>], 183 | Length = length(L), 184 | ?assertEqual(0, erldis_list:len(<<"foo">>, Client)), 185 | erldis_list:from_list(L, <<"foo">>, Client), 186 | ?assertEqual(Length, erldis_list:len(<<"foo">>, Client)), 187 | 188 | F = fun(Item) -> 189 | N = Length - erldis_list:len(<<"foo">>, Client), 190 | ?assertEqual(lists:nth(N, L), Item) 191 | end, 192 | 193 | erldis_list:out_foreach(F, <<"foo">>, Client), 194 | ?assertEqual(0, erldis_list:len(<<"foo">>, Client)), 195 | erldis_client:stop(Client). 196 | 197 | setup() -> 198 | % setup 199 | application:load(erldis), 200 | {ok, Client} = erldis_client:connect(), 201 | ?assertEqual(erldis:flushdb(Client), ok), 202 | Client. 203 | -------------------------------------------------------------------------------- /test/erldis_multiple.erl: -------------------------------------------------------------------------------- 1 | -module(erldis_multiple). 2 | -export([test/0, get_key/1, server/2]). 3 | 4 | test() -> 5 | {ok, Conn} = erldis:connect("localhost", 6379), 6 | erldis:zadd(Conn, "erldis_fail", 4.0, "thing1"), 7 | erldis:zadd(Conn, "erldis_fail", 4.0, "thing2"), 8 | erldis:zadd(Conn, "erldis_fail", 3.5, "thing3"), 9 | erldis:zadd(Conn, "erldis_fail", 2.0, "thing4"), 10 | erldis:zadd(Conn, "erldis_fail", 6.3, "thing5"), 11 | io:format("Expected Result: ~n"), 12 | io:format("~w~n", [erldis:zrevrange(Conn, "erldis_fail", 0, 30)]), 13 | io:format("SNIP ~n~n"), 14 | %Pid = spawn_link(erldis_multiple, server, [self(), Conn]), 15 | run_test(Conn, 1000). 16 | 17 | get_key(Conn) -> 18 | %erldis:zrevrange_withscores(Conn, "erldis_fail", 0, 30). 19 | %Res = send(Conn, fun(Redis)-> 20 | Res = erldis:zrevrange(Conn, "erldis_fail", 0, 30), 21 | %end), 22 | try 5 = length(Res) 23 | catch 24 | error:{badmatch, _} -> 25 | io:format("~w~n~n", [Res]), 26 | erlang:error(unexpected_result) 27 | end. 28 | 29 | run_test(_Conn, 0) -> 30 | %receive done -> done end; 31 | done; 32 | run_test(Conn, N) -> 33 | spawn_link(erldis_multiple, get_key, [Conn]), 34 | run_test(Conn, N-1). 35 | 36 | send(Pid, Fun)-> 37 | Pid ! {self(), Fun}, 38 | receive {redis, Res} -> 39 | Res 40 | end. 41 | 42 | server(Laucher, Conn) -> 43 | receive {Pid, Fun}-> 44 | Pid ! {redis, Fun(Conn)}, 45 | server(Laucher,Conn) 46 | after 1000 -> 47 | Laucher ! done 48 | end. 49 | -------------------------------------------------------------------------------- /test/erldis_pipelining_tests.erl: -------------------------------------------------------------------------------- 1 | -module(erldis_pipelining_tests). 2 | 3 | -include_lib("eunit/include/eunit.hrl"). 4 | -include("erldis.hrl"). 5 | 6 | pipelining_test()-> 7 | {ok, Client} = erldis:connect("localhost", 6379), 8 | ?assertEqual(erldis:flushdb(Client), ok), 9 | erldis:set_pipelining(Client, true), 10 | erldis:get(Client, <<"pippo">>), 11 | erldis:set(Client, <<"hello">>, <<"kitty!">>), 12 | erldis:get(Client, <<"hello">>), 13 | erldis:setnx(Client, <<"foo">>, <<"bar">>), 14 | erldis:setnx(Client, <<"foo">>, <<"bar">>), 15 | ?assertEqual([nil, ok, [<<"kitty!">>],true, false],erldis:get_all_results(Client)). -------------------------------------------------------------------------------- /test/erldis_pubsub_tests.erl: -------------------------------------------------------------------------------- 1 | -module(erldis_pubsub_tests). 2 | 3 | -include_lib("eunit/include/eunit.hrl"). 4 | -include("erldis.hrl"). 5 | 6 | publish_basic_test()-> 7 | {ok, Client} = erldis:connect("localhost", 6379), 8 | % no one follows -> returns 0 9 | ?assertEqual(0, erldis:publish(Client, <<"foo">>, <<"bar">>)), 10 | {ok, Client2} = erldis:connect("localhost", 6379), 11 | ?assertEqual(1, erldis:subscribe(Client2, <<"foo">>, self())), 12 | ?assertEqual({<<"foo">>,0}, erldis:unsubscribe(Client2, <<"foo">>)). 13 | 14 | subscribe_basic_test()-> 15 | {ok, Client} = erldis:connect("localhost", 6379), 16 | ?assertEqual(1, erldis:subscribe(Client, <<"foo">>, self())), 17 | ?assertEqual(2, erldis:subscribe(Client, <<"bar">>, self())). 18 | 19 | unsubscribe_basic_test()-> 20 | {ok, Client} = erldis:connect("localhost", 6379), 21 | ?assertEqual(1, erldis:subscribe(Client, <<"foo1">>, self())), 22 | ?assertEqual({<<"foo1">>,0}, erldis:unsubscribe(Client, <<"foo1">>)), 23 | ?assertEqual(1, erldis:subscribe(Client, <<"foo1">>, self())), 24 | ?assertEqual(2, erldis:subscribe(Client, <<"foo2">>, self())), 25 | ?assertEqual({<<"foo1">>,1}, erldis:unsubscribe(Client)). 26 | 27 | pubsub_test()-> 28 | {ok, Pub} = erldis:connect("localhost", 6379), 29 | {ok, Sub} = erldis:connect("localhost", 6379), 30 | ?assertEqual(1, erldis:subscribe(Sub, <<"bidule">>, self())), 31 | ?assertEqual(1, erldis:publish(Pub, <<"bidule">>, <<"bar">>)), 32 | receive 33 | {message, <<"bidule">>, Message} -> 34 | ?assertEqual(<<"bar">>, Message) 35 | after 36 | 100 -> 37 | ?assertEqual(true, false) %TODO not proud 38 | end. 39 | 40 | pubsub_multiple_test()-> 41 | {ok, Pub} = erldis:connect("localhost", 6379), 42 | {ok, Sub} = erldis:connect("localhost", 6379), 43 | ?assertEqual(1, erldis:subscribe(Sub, <<"multibidule">>, self())), 44 | ?assertEqual(1, erldis:publish(Pub, <<"multibidule">>, <<"bar">>)), 45 | receive 46 | {message, <<"multibidule">>, M1} -> 47 | ?assertEqual(<<"bar">>, M1) 48 | after 49 | 100 -> 50 | ?assertEqual(true, false) %TODO not proud 51 | end, 52 | ?assertEqual(1, erldis:publish(Pub, <<"multibidule">>, <<"baz">>)), 53 | receive 54 | {message, <<"multibidule">>, M2} -> 55 | ?assertEqual(<<"baz">>, M2) 56 | after 57 | 100 -> 58 | ?assertEqual(true, false) %TODO not proud 59 | end. -------------------------------------------------------------------------------- /test/erldis_sets_tests.erl: -------------------------------------------------------------------------------- 1 | -module(erldis_sets_tests). 2 | 3 | -include_lib("eunit/include/eunit.hrl"). 4 | 5 | sets_test() -> 6 | % setup 7 | application:load(erldis), 8 | {ok, Client} = erldis_client:connect(), 9 | ?assertEqual(erldis:flushdb(Client), ok), 10 | % non existent set 11 | ?assertEqual(erldis_sets:is_set(Client, <<"foo">>), false), 12 | ?assertEqual(erldis_sets:to_list(Client, <<"foo">>), []), 13 | ?assertEqual(erldis_sets:size(Client, <<"foo">>), 0), 14 | ?assertEqual(erldis_sets:is_element(<<"bar">>, Client, <<"foo">>), false), 15 | % add 1 element, values must be strings/lists, can't be integers 16 | erldis_sets:add_element(<<"1">>, Client, <<"foo">>), 17 | ?assertEqual(erldis_sets:is_set(Client, <<"foo">>), true), 18 | ?assertEqual(erldis_sets:size(Client, <<"foo">>), 1), 19 | ?assertEqual(erldis_sets:is_element(<<"1">>, Client, <<"foo">>), true), 20 | ?assertEqual(erldis_sets:to_list(Client, <<"foo">>), [<<"1">>]), 21 | % add 2 element 22 | erldis_sets:add_element(<<"2">>, Client, <<"foo">>), 23 | ?assertEqual(erldis_sets:size(Client, <<"foo">>), 2), 24 | ?assertEqual(erldis_sets:is_element(<<"2">>, Client, <<"foo">>), true), 25 | ?assertEqual(lists:sort(erldis_sets:to_list(Client, <<"foo">>)), [<<"1">>, <<"2">>]), 26 | % del 2 element 27 | erldis_sets:del_element(<<"2">>, Client, <<"foo">>), 28 | ?assertEqual(erldis_sets:size(Client, <<"foo">>), 1), 29 | ?assertEqual(erldis_sets:is_element(<<"2">>, Client, <<"foo">>), false), 30 | % from list 31 | Elems = [<<"a">>, <<"b">>, <<"c">>], 32 | erldis_sets:from_list(Client, <<"foo">>, Elems), 33 | ?assertEqual(lists:sort(erldis_sets:to_list(Client, <<"foo">>)), Elems), 34 | erldis_client:stop(Client). 35 | % TODO: test union, intersection, is_disjoint, subtract. 36 | 37 | combo_sets_test() -> 38 | application:load(erldis), 39 | {ok, Client} = erldis_client:connect(), 40 | ?assertEqual(ok, erldis:flushdb(Client)), 41 | 42 | ?assertEqual(true, erldis_sets:add_element(<<"1">>, Client, <<"foo">>)), 43 | ?assertEqual(true, erldis_sets:add_element(<<"2">>, Client, <<"foo">>)), 44 | ?assertEqual(true, erldis_sets:add_element(<<"3">>, Client, <<"foo">>)), 45 | 46 | ?assertEqual(true, erldis_sets:add_element(<<"2">>, Client, <<"bar">>)), 47 | ?assertEqual(true, erldis_sets:add_element(<<"3">>, Client, <<"bar">>)), 48 | ?assertEqual(true, erldis_sets:add_element(<<"4">>, Client, <<"bar">>)), 49 | 50 | Union = erldis_sets:union(Client, [<<"foo">>, <<"bar">>]), 51 | ?assertEqual([<<"1">>, <<"2">>, <<"3">>, <<"4">>], lists:sort(Union)), 52 | ?assertEqual(4, erldis:sunionstore(Client, <<"union">>, [<<"foo">>, <<"bar">>])), 53 | ?assertEqual(lists:sort(Union), lists:sort(erldis:smembers(Client, <<"union">>))), 54 | 55 | ?assertEqual([<<"1">>], erldis_sets:subtract(Client, <<"foo">>, <<"bar">>)), 56 | ?assertEqual([<<"4">>], erldis_sets:subtract(Client, <<"bar">>, <<"foo">>)), 57 | ?assertEqual(1, erldis:sdiffstore(Client, <<"diff">>, [<<"foo">>, <<"bar">>])), 58 | ?assertEqual([<<"1">>], erldis:smembers(Client, <<"diff">>)), 59 | 60 | Inter = erldis_sets:intersection(Client, [<<"foo">>, <<"bar">>]), 61 | ?assertEqual([<<"2">>, <<"3">>], lists:sort(Inter)), 62 | ?assertEqual(2, erldis:sinterstore(Client, <<"inter">>, [<<"foo">>, <<"bar">>])), 63 | ?assertEqual(lists:sort(Inter), lists:sort(erldis:smembers(Client, <<"inter">>))). -------------------------------------------------------------------------------- /test/erldis_tests.erl: -------------------------------------------------------------------------------- 1 | -module(erldis_tests). 2 | 3 | -include_lib("eunit/include/eunit.hrl"). 4 | -include("erldis.hrl"). 5 | 6 | quit_test() -> 7 | {ok, Client} = erldis:connect("localhost", 6379), 8 | ?assertEqual(shutdown, erldis:quit(Client)), 9 | false = is_process_alive(Client). 10 | 11 | utils_test() -> 12 | ?assertEqual(<<"1">>, erldis_binaries:to_binary(1)), 13 | ?assertEqual(<<"atom">>, erldis_binaries:to_binary(atom)). 14 | 15 | basic_test() -> 16 | {ok, Client} = erldis:connect("localhost", 6379), 17 | ?assertEqual(ok, erldis:flushdb(Client)), 18 | 19 | ?assertEqual(nil, erldis:get(Client, <<"pippo">>)), 20 | ok = erldis:set(Client, <<"hello">>, <<"kitty!">>), 21 | ?assert(erldis:setnx(Client, <<"foo">>, <<"bar">>)), 22 | ?assertNot(erldis:setnx(Client, <<"foo">>, <<"bar">>)), 23 | 24 | ?assert(erldis:exists(Client, <<"hello">>)), 25 | ?assert(erldis:exists(Client, <<"foo">>)), 26 | ?assertEqual(<<"bar">>, erldis:get(Client, <<"foo">>)), 27 | ?assertEqual([<<"kitty!">>, <<"bar">>], erldis:mget(Client, [<<"hello">>, <<"foo">>])), 28 | ?assertEqual([<<"foo">>], erldis:keys(Client, <<"f*">>)), 29 | erldis:set(Client,<<"foo1">>,<<"bar">>), 30 | erldis:set(Client,<<"foo2">>,<<"bar">>), 31 | erldis:set(Client,<<"foo3">>,<<"bar">>), 32 | ?assertEqual(lists:sort([<<"foo">>,<<"foo1">>,<<"foo2">>,<<"foo3">>]), 33 | lists:sort(erldis:keys(Client, <<"f*">>))), 34 | erldis:set(Client,<<"quux">>,<<"ohai">>), 35 | ?assertEqual([<<"quux">>], erldis:keys(Client, <<"q*">>)), 36 | 37 | 38 | ?assert(erldis:del(Client, <<"hello">>)), 39 | ?assert(erldis:del(Client, <<"foo">>)), 40 | ?assertNot(erldis:exists(Client, <<"hello">>)), 41 | ?assertNot(erldis:exists(Client, <<"foo">>)), 42 | 43 | ?assertEqual(shutdown, erldis:quit(Client)). 44 | 45 | set_test() -> 46 | {ok, Client} = erldis:connect("localhost", 6379), 47 | ?assertEqual(ok, erldis:flushdb(Client)), 48 | erldis:sadd(Client, <<"set">>, <<"toto">>), 49 | ?assertEqual([<<"toto">>], erldis:smembers(Client, <<"set">>)), 50 | erldis:srem(Client, <<"set">>, <<"toto">>), 51 | ?assertEqual([], erldis:smembers(Client, <<"set">>)), 52 | ?assertEqual(shutdown, erldis:quit(Client)). 53 | 54 | hash_test() -> 55 | {ok, Client} = erldis:connect(), 56 | ?assertEqual(ok, erldis:flushdb(Client)), 57 | ?assertEqual(true, erldis:hset(Client, <<"key">>, <<"field">>, <<"value">>)), 58 | ?assertEqual(ok, erldis:hmset(Client, <<"key2">>, [<<"field">>, <<"value2">>])), 59 | ?assertEqual(ok, erldis:hmset(Client, <<"key2">>, [<<"fieldM">>, <<"valueM">>, <<"fieldK">>, <<"valueK">>])), 60 | ?assertEqual(<<"value">>, erldis:hget(Client, <<"key">>, <<"field">>)), 61 | ?assertEqual(<<"value2">>, erldis:hget(Client, <<"key2">>, <<"field">>)), 62 | ?assertEqual(<<"valueK">>, erldis:hget(Client, <<"key2">>, <<"fieldK">>)), 63 | ?assertEqual(20, erldis:hincrby(Client, <<"increment-key">>, <<"by-20">>, 20)), 64 | ?assertEqual(40, erldis:hincrby(Client, <<"increment-key">>, <<"by-20">>, 20)), 65 | ?assertEqual(<<"40">>, erldis:hget(Client, <<"increment-key">>, <<"by-20">>)), 66 | ?assertEqual(true, erldis:hdel(Client, <<"increment-key">>, <<"by-20">>)), 67 | ?assertEqual(false, erldis:hdel(Client, <<"increment-key">>, <<"by-20">>)), 68 | ?assertEqual(20, erldis:hincrby(Client, <<"increment-key">>, <<"by-20">>, 20)), 69 | ?assertEqual(1, erldis:hlen(Client, <<"increment-key">>)), 70 | ?assertEqual(1, erldis:hlen(Client, <<"key">>)), 71 | ?assertEqual(true, erldis:hexists(Client, <<"key">>, <<"field">>)), 72 | ?assertEqual(false, erldis:hexists(Client, <<"key">>, <<"non-field">>)), 73 | ?assertEqual([<<"field">>], erldis:hkeys(Client, <<"key">>)), 74 | ?assertEqual([<<"by-20">>], erldis:hkeys(Client, <<"increment-key">>)), 75 | ?assertEqual([<<"by-20">>, <<"20">>], erldis:hgetall(Client, <<"increment-key">>)), 76 | ?assertEqual([<<"field">>, <<"value">>], erldis:hgetall(Client, <<"key">>)). 77 | 78 | list_test() -> 79 | {ok, Client} = erldis:connect("localhost", 6379), 80 | ?assertEqual(ok, erldis:flushdb(Client)), 81 | 82 | ?assertEqual([], erldis:lrange(Client, <<"foo">>, 1, 2)), 83 | 1 = erldis:rpush(Client, <<"a_list">>, <<"1">>), 84 | 2 = erldis:rpush(Client, <<"a_list">>, <<"2">>), 85 | 3 = erldis:rpush(Client, <<"a_list">>, <<"3">>), 86 | 4 = erldis:rpush(Client, <<"a_list">>, <<"1">>), 87 | ?assertEqual(1, erldis:lrem(Client, <<"a_list">>, 1, <<"1">>)), 88 | ?assertEqual([<<"2">>, <<"3">>, <<"1">>], erldis:lrange(Client, <<"a_list">>, 0, 2)), 89 | ?assertEqual([<<"1">>, <<"2">>, <<"3">>], erldis:sort(Client, <<"a_list">>)), 90 | ?assertEqual([<<"3">>, <<"2">>, <<"1">>], erldis:sort(Client, <<"a_list">>, <<"DESC">>)), 91 | ?assertEqual([<<"1">>, <<"2">>], erldis:sort(Client, <<"a_list">>, <<"LIMIT 0 2 ASC">>)), 92 | 93 | ?assertEqual(shutdown, erldis:quit(Client)). 94 | 95 | zset_test() -> 96 | {ok, Client} = erldis:connect("localhost", 6379), 97 | ?assertEqual(ok, erldis:flushdb(Client)), 98 | 99 | ?assertEqual(0, erldis:zcard(Client, <<"foo">>)), 100 | ?assertEqual([], erldis:zrange(Client, <<"foo">>, 0, 1)), 101 | ?assertEqual(0, erldis:zscore(Client, <<"foo">>, <<"elem1">>)), 102 | 103 | ?assertEqual(true, erldis:zadd(Client, <<"foo">>, 5, <<"elem1">>)), 104 | ?assertEqual([<<"elem1">>], erldis:zrange(Client, <<"foo">>, 0, 1)), 105 | ?assertEqual([<<"elem1">>], erldis:zrevrange(Client, <<"foo">>, 0, 1)), 106 | ?assertEqual([{<<"elem1">>, 5}], erldis:zrange_withscores(Client, <<"foo">>, 0, 1)), 107 | ?assertEqual([{<<"elem1">>, 5}], erldis:zrevrange_withscores(Client, <<"foo">>, 0, 1)), 108 | ?assertEqual(false, erldis:zadd(Client, <<"foo">>, 6, <<"elem1">>)), 109 | ?assertEqual(1, erldis:zcard(Client, <<"foo">>)), 110 | ?assertEqual(6, erldis:zscore(Client, <<"foo">>, <<"elem1">>)), 111 | ?assertEqual(8, erldis:zincrby(Client, <<"foo">>, 2, <<"elem1">>)), 112 | % can use list keys & values too 113 | ?assertEqual(true, erldis:zadd(Client, "foo", 1.5, "a-elem")), 114 | ?assertEqual(2, erldis:zcard(Client, "foo")), 115 | ?assertEqual(1.5, erldis:zscore(Client, "foo", "a-elem")), 116 | ?assertEqual([<<"a-elem">>, <<"elem1">>], erldis:zrange(Client, "foo", 0, 2)), 117 | ?assertEqual([<<"elem1">>, <<"a-elem">>], erldis:zrevrange(Client, "foo", 0, 2)), 118 | ?assertEqual([{<<"a-elem">>, 1.5}, {<<"elem1">>, 8}], erldis:zrange_withscores(Client, "foo", 0, 2)), 119 | ?assertEqual([{<<"elem1">>, 8}, {<<"a-elem">>, 1.5}], erldis:zrevrange_withscores(Client, "foo", 0, 2)), 120 | ?assertEqual([<<"a-elem">>], erldis:zrangebyscore(Client, "foo", 1.0, 2.0)), 121 | ?assertEqual([<<"a-elem">>], erldis:zrangebyscore(Client, "foo", 1, 10, 0, 1)), 122 | ?assertEqual([<<"a-elem">>, <<"elem1">>], erldis:zrangebyscore(Client, "foo", 1, 10, 0, 2)), 123 | ?assertEqual([<<"elem1">>], erldis:zrangebyscore(Client, "foo", 1, 10, 1, 2)), 124 | ?assertEqual([], erldis:zrangebyscore(Client, "foo", 1, 10, 2, 2)), 125 | ?assertEqual(1, erldis:zremrangebyscore(Client, "foo", 1, 2)), 126 | ?assertEqual(false, erldis:zrem(Client, "foo", "a-elem")), 127 | ?assertEqual(1, erldis:zcard(Client, "foo")), 128 | ?assertEqual([<<"elem1">>], erldis:zrevrange(Client, "foo", 0, 1)), 129 | 130 | ?assertEqual(true, erldis:zrem(Client, <<"foo">>, <<"elem1">>)), 131 | ?assertEqual(false, erldis:zrem(Client, <<"foo">>, <<"elem1">>)), 132 | ?assertEqual(0, erldis:zcard(Client, <<"foo">>)), 133 | ?assertEqual([], erldis:zrange(Client, <<"foo">>, 0, 2)), 134 | 135 | ?assertEqual(shutdown, erldis:quit(Client)). 136 | 137 | % inline_tests(Client) -> 138 | % [?_assertMatch(ok, erldis:set(Client, <<"hello">>, <<"kitty!">>)), 139 | % ?_assertMatch(false, erldis:setnx(Client, <<"hello">>, <<"kitty!">>)), 140 | % ?_assertMatch(true, erldis:exists(Client, <<"hello">>)), 141 | % ?_assertMatch(true, erldis:del(Client, <<"hello">>)), 142 | % ?_assertMatch(false, erldis:exists(Client, <<"hello">>)), 143 | % 144 | % ?_assertMatch(true, erldis:setnx(Client, <<"hello">>, <<"kitty!">>)), 145 | % ?_assertMatch(true, erldis:exists(Client, <<"hello">>)), 146 | % ?_assertMatch("kitty!">>, erldis:get(Client, <<"hello">>)), 147 | % ?_assertMatch(true, erldis:del(Client, <<"hello">>)), 148 | % 149 | % 150 | % ?_assertMatch(1, erldis:incr(Client, <<"pippo">>)) 151 | % ,?_assertMatch(2, erldis:incr(Client, <<"pippo">>)) 152 | % ,?_assertMatch(1, erldis:decr(Client, <<"pippo">>)) 153 | % ,?_assertMatch(0, erldis:decr(Client, <<"pippo">>)) 154 | % ,?_assertMatch(-1, erldis:decr(Client, <<"pippo">>)) 155 | % 156 | % ,?_assertMatch(6, erldis:incrby(Client, <<"pippo">>, 7)) 157 | % ,?_assertMatch(2, erldis:decrby(Client, <<"pippo">>, 4)) 158 | % ,?_assertMatch(-2, erldis:decrby(Client, <<"pippo">>, 4)) 159 | % ,?_assertMatch(true, erldis:del(Client, <<"pippo">>)) 160 | % ]. 161 | -------------------------------------------------------------------------------- /test/proto_tests.erl: -------------------------------------------------------------------------------- 1 | -module(proto_tests). 2 | 3 | -include_lib("eunit/include/eunit.hrl"). 4 | 5 | parse_test() -> 6 | ok = erldis_proto:parse(empty, <<"+OK">>), 7 | pong = erldis_proto:parse(empty, <<"+PONG">>), 8 | false = erldis_proto:parse(empty, <<":0">>), 9 | true = erldis_proto:parse(empty, <<":1">>), 10 | {error, <<"1">>} = erldis_proto:parse(empty, <<"-1">>). 11 | --------------------------------------------------------------------------------