forked from yfinkelstein/node-zookeeper
/
zk_test_shootout_promise.js
161 lines (142 loc) · 6.52 KB
/
zk_test_shootout_promise.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/*
Shootout is played by the following rules:
- Each games has 2 players A and B that are "shooting"" at each other's gate
- Gate is a zookeeper namespace node with a known path: /shootout_<game index>_<A|B>
- Shoot is an act of creating a child node in the opponent's gate
- Defence is an act of deleting all nodes in the player's own gate
- Upon "go" command, both player engage in shooting and self-defending routines.
- This demostrates the following APIs of the zookeeper interface:
create, get_children with a watcher, delete
- Also, each of the players uses his own ZK session
- The beauty of this implementation is that because of the async nature of both zookeeper and node.js
Defend () and Shoot() operations can run in parallel without blocking each other. This creates the effect of
concurrency although only 1 thread is actually used. Here is what you can see when you start 3 games at the
same time and kill it with ^C after a while:
====> Game summary:
game #0:
playerA made 1978 shots, defended 1958 shots
playerB made 1960 shots, defended 1978 shots
game #1:
playerA made 1965 shots, defended 1962 shots
playerB made 1962 shots, defended 1965 shots
game #2:
playerA made 1965 shots, defended 1966 shots
playerB made 1966 shots, defended 1964 shots
Also note how the looping is implemented. Recursion in node.js does not cause stack growth. It's not a tail recursion either...
*/
var promise = require("../lib/promise");
var ZK = require("../lib/zk_promise").ZK;
var NGames = parseInt (process.argv[2] || 1);
var connect = (process.argv[3] || 'localhost:2181');
var zk_config = {connect:connect, debug_level:ZK.ZOO_LOG_LEVEL_WARN, timeout:20000, host_order_deterministic:false};
function Game (game_number, base_path) {
var start_promise = promise.defer();
function createContext (name) {
return {
name:game_number + "_" + name,
ready_promise:promise.defer(),
my_gate:"",
other_gate:"",
my_shots:0,
his_shots:0
};
};
var playerAContext = this.playerAContext = createContext ("A");
var playerBContext = this.playerBContext = createContext ("B");
function Player (context) {
var zk = new ZK ().init (zk_config);
zk.on_connected().
then ( // create my own gate node
function (zkk){
console.log ("player %s on_connected: zk=%j", context.name, zkk);
return zkk.create (base_path + context.name, "target", 0/*ZK.ZOO_SEQUENCE*/);
}
).then ( // store the actual gate node path in the context and fire "I'm ready"
function (actual_gate_path) {
console.log ("player %s created his gate at=%s", context.name, actual_gate_path);
context.my_gate = actual_gate_path;
context.ready_promise.resolve ();
return start_promise;
},
function (error) {
if (error == ZK.ZNODEEXISTS) {
context.my_gate = base_path + context.name;
context.ready_promise.resolve ();
return start_promise;
} else {
throw new Error (error);
}
}
).then ( // game is on: shoot!
function () {
Defend ();
Shoot ();
}
);
var Shoot = function () {
console.log ("====>player %s says: I made %d shots, he made %s shots", context.name, context.my_shots, context.his_shots);
console.log ("player %s is about to attack", context.name);
zk.create (context.other_gate + "/attack", "kick", ZK.ZOO_SEQUENCE | ZK.ZOO_EPHEMERAL).then (
function (created_node) {
console.log ("player %s attacked with %s", context.name, created_node);
context.my_shots ++;
Shoot ();
}
)
};
var Defend = function () {
zk.w_get_children (context.my_gate,
function (type, state, path) { //this is my defence watcher
console.log ("defense watcher triggered for player %s: type=%d, path=%s", context.name,type, path);
Defend ();
}
).then (
function (children) {// these are attacks against me; I have to stop them
if (!children || !children.length ) return;
var delete_promisses = [];
children.forEach (
function (child) {
var ch_path = context.my_gate+"/"+child;
console.log ("player %s is stopping attack %s", context.name, ch_path);
var prm = zk.delete_ (ch_path, -1);
delete_promisses.push (prm);
context.his_shots ++;
}
);
promise.all (delete_promisses).then (
function () { //all children where deleted
console.log ("player %s stopped %d attacks: %j", context.name, delete_promisses.length, children);
}
);
}
);
};
}; // end of Player
var pA = Player (playerAContext);
var pB = Player (playerBContext);
promise.all ([playerAContext.ready_promise, playerBContext.ready_promise]).then (
function () {
playerAContext.other_gate = playerBContext.my_gate;
playerBContext.other_gate = playerAContext.my_gate;
console.log ("------- GAME #%d is ON!--------", game_number);
start_promise.resolve ();
}
);
};
var games = [];
do {
games.push (new Game (games.length, "/shootout_"));
} while (games.length < NGames);
process.on ("SIGINT",
function () {
console.log ("\n\n====> Game summary:");
games.forEach (
function (game, i) {
console.log ("game #%d: ", i);
console.log ("\tplayerA made %s shots, defended %d shots", game.playerAContext.my_shots, game.playerAContext.his_shots );
console.log ("\tplayerB made %s shots, defended %d shots", game.playerBContext.my_shots, game.playerBContext.his_shots );
}
);
process.exit (0);
}
);