├── .gitignore ├── run_tests.sh ├── lib ├── tools.js ├── gearnode.js └── gearman-connection.js ├── package.json ├── LICENSE ├── examples ├── client.js └── worker.js ├── README.md └── test └── tests.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/node 2 | 3 | require.paths.push(__dirname); 4 | require.paths.push(__dirname + '/lib'); 5 | var testrunner = require('nodeunit').testrunner; 6 | 7 | process.chdir(__dirname); 8 | testrunner.run(['test']); -------------------------------------------------------------------------------- /lib/tools.js: -------------------------------------------------------------------------------- 1 | 2 | function packInt(nr, bytelen){ 3 | if(!bytelen){ 4 | bytelen = Math.floor(Math.log(nr) / Math.floor(255)) + 1; 5 | } 6 | 7 | bytes = Buffer(Number(bytelen) || 4); 8 | 9 | for(var i=bytelen-1; i>=0; i--){ 10 | bytes[i] = nr & (255); 11 | nr = nr >> 8; 12 | } 13 | 14 | return new Buffer(bytes); 15 | } 16 | 17 | function unpackInt(bytes){ 18 | var nr = 0; 19 | for(var i=bytes.length-1; i >= 0; i--){ 20 | nr += (Math.pow(256, bytes.length - i - 1)) * bytes[i]; 21 | } 22 | return nr; 23 | } 24 | 25 | module.exports.packInt = packInt; 26 | module.exports.unpackInt = unpackInt; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gearnode", 3 | "description": "Gearman client/worker module for Node.JS", 4 | "version": "0.1.2", 5 | "author" : "Andris Reinman", 6 | "maintainers":[ 7 | { 8 | "name":"andris", 9 | "email":"andris@node.ee" 10 | } 11 | ], 12 | "homepage": "http://github.com/andris9/gearnode", 13 | "repository" : { 14 | "type" : "git", 15 | "url" : "http://github.com/andris9/nodemailer.git" 16 | }, 17 | "main" : "./lib/gearnode", 18 | "licenses" : [ 19 | { 20 | "type": "MIT", 21 | "url": "http://github.com/andris9/gearnode/blob/master/LICENSE" 22 | } 23 | ], 24 | "dependencies": { 25 | "optimist":"*", 26 | "nodeunit":"*" 27 | }, 28 | "engine": [ "node >=0.3.0" ], 29 | "keywords": ["gearman", "worker", "message queue"] 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Andris Reinman 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 11 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 12 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 13 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 14 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 15 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 16 | SOFTWARE. -------------------------------------------------------------------------------- /examples/client.js: -------------------------------------------------------------------------------- 1 | var Gearman = require("../lib/gearnode"); 2 | 3 | client = new Gearman(); 4 | client.addServer("localhost", 7003); 5 | 6 | client.getExceptions(function(err, success){ 7 | console.log(success && "Registered for exceptions" || "No exceptions"); 8 | }); 9 | 10 | var job = client.submitJob("sqr", -25, {encoding:"number"}); 11 | 12 | job.on("created", function(handle){ 13 | console.log("Job created as '"+handle+"'"); 14 | }); 15 | 16 | job.on("complete", function(response){ 17 | console.log("Job ready: '"+response+"'"); 18 | client.end(); 19 | }); 20 | 21 | job.on("fail", function(){ 22 | console.log("Job failed"); 23 | client.end(); 24 | }); 25 | 26 | job.on("exception", function(message){ 27 | console.log("Exception '"+message+"'"); 28 | }); 29 | 30 | job.on("warning", function(message){ 31 | console.log("Warning '"+message+"'"); 32 | }); 33 | 34 | job.on("data", function(message){ 35 | console.log("Data '"+message+"'"); 36 | }); 37 | 38 | job.on("status", function(nu, de){ 39 | console.log("Status "+nu+" / "+de); 40 | }); 41 | 42 | -------------------------------------------------------------------------------- /examples/worker.js: -------------------------------------------------------------------------------- 1 | var Gearman = require("../lib/gearnode"); 2 | 3 | String.prototype.reverse = function(){ 4 | splitext = this.split(""); 5 | revertext = splitext.reverse(); 6 | reversed = revertext.join(""); 7 | return reversed; 8 | }; 9 | 10 | worker= new Gearman(); 11 | worker.addServer("localhost", 7003); 12 | worker.setWorkerId("testkast"); 13 | 14 | worker.addFunction("reverse", "utf-8", function(payload, job){ 15 | var str = payload, 16 | reversed = str.reverse(); 17 | 18 | setTimeout(function(){ 19 | job.data("data part"); 20 | 21 | setTimeout(function(){ 22 | job.warning("something strange happened!"); 23 | 24 | setTimeout(function(){ 25 | job.setStatus(50, 100); 26 | 27 | setTimeout(function(){ 28 | 29 | job.complete(reversed); 30 | 31 | },500); 32 | 33 | },500); 34 | },500); 35 | },500); 36 | }); 37 | 38 | worker.addFunction("sqr", "number", function(payload, job){ 39 | if(payload < 0){ 40 | job.warning("Used number is smaller than zero!"); 41 | } 42 | job.complete(payload * payload); 43 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NB !!ABANDONWARE!! 2 | 3 | I'm not maintaining this repo anymore. Sorry. 4 | 5 | # Gearnode 6 | 7 | **Gearnode** is a Node.JS client/worker module for Gearman. 8 | 9 | **NB!** check out another Gearman library of mine [node-gearman](https://github.com/andris9/node-gearman) which is somewhat more simple than this one. 10 | 11 | ## Installation 12 | 13 | npm install gearnode 14 | 15 | ## Tests 16 | 17 | Tests are run with *nodeunit* 18 | 19 | ./run_tests.sh 20 | 21 | Tests expect a Gearman daemon running on port 7003 22 | 23 | ## Usage 24 | 25 | ### Worker 26 | 27 | var Gearnode = require("gearnode"); 28 | 29 | worker = new Gearnode(); 30 | worker.addServer(); // use localhost 31 | 32 | worker.addFunction("upper", "utf-8", function(payload, job){ 33 | var response = payload.toUpperCase(); 34 | job.complete(response); 35 | }); 36 | 37 | ### Client 38 | 39 | var Gearnode = require("gearnode"); 40 | 41 | client = new Gearnode(); 42 | client.addServer(); 43 | 44 | var job = client.submitJob("upper", "hello world!", {encoding:'utf-8'}); 45 | 46 | job.on("complete", function(handle, response){ 47 | console.log(response); // HELLO WORLD! 48 | client.end(); 49 | }); 50 | 51 | ## API 52 | 53 | ### Require Gearman library 54 | 55 | var Gearnode = require("gearnode"); 56 | 57 | ### Create a new Gearnode worker/client 58 | 59 | var gearnode = new Gearnode(); 60 | 61 | ### Add a server 62 | 63 | gearnode.addServer([host][, port]) 64 | 65 | Where 66 | 67 | * **host** is the hostname of the Gearman server (defaults to localhost); 68 | * **port** is the Gearman port (default is 4730) 69 | 70 | Example 71 | 72 | gearnode.addServer(); // use default values 73 | gearnode.addServer("gearman.lan", 7003); 74 | 75 | ### Register for exceptions 76 | 77 | Exceptions are not sent to the client by default. To turn these on use the following command. 78 | 79 | gearnode.getExceptions([callback]) 80 | 81 | Where 82 | 83 | * **callback** is an optional callback function with params *error* if an error occured and *success* which is true if the command succeeded 84 | 85 | Example 86 | 87 | client = new Gearnode(); 88 | client.addServer(); // use default values 89 | client.getExceptions(); 90 | 91 | job = client.submitJob("reverse", "Hello world!"); 92 | 93 | job.on("error", function(exception){ 94 | console.log(exception); 95 | }); 96 | 97 | ### Assign an ID for the Worker 98 | 99 | Worker ID's identify unique workers for monitoring Gearman. 100 | 101 | gearnode.setWorkerId(id) 102 | 103 | Where 104 | 105 | * **id** is a string that will act as the name for the worker 106 | 107 | Example 108 | 109 | worker = new Gearnode(); 110 | worker.addServer(); // use default values 111 | worker.setWorkerId("my_worker"); 112 | 113 | ### Submit a job 114 | 115 | gearnode.submitJob(func, payload[, options]) 116 | 117 | Where 118 | 119 | * **func** is the function name 120 | * **payload** is either a String or a Buffer value 121 | * **options** is an optional options object (see below) 122 | 123 | Possible option values 124 | 125 | * **encoding** - indicates the encoding for the job response (default is Buffer). Can be "utf-8", "ascii", "base64", "number" or "buffer" 126 | * **background** - if set to true, detach the job from the client (complete and error events will not be sent to the client) 127 | * **priority** - indicates the priority of the job. Possible values "low", "normal" (default) and "high" 128 | 129 | Returns a Client Job object with the following events 130 | 131 | * **created** - when the function is queued by the server (params: handle value) 132 | * **complete** - when the function returns (params: response data in encoding specified by the options value) 133 | * **fail** - when the function fails (params: none) 134 | * **error** - when an exception is thrown (params: error string) 135 | * **warning** - when a warning is sent (params: warning string) 136 | * **status** - when the status of a long running function is updated (params: numerator numbber, denominator number) 137 | * **data** - when partial data is available (params: response data in encoding specified by the options value) 138 | 139 | Example 140 | 141 | client = new Gearnode(); 142 | client.addServer(); // use default values 143 | worker.getExceptions(); 144 | 145 | job = client.submitJob("reverse", "Hello world!", {encoding:"utf-8"}); 146 | 147 | job.on("complete", function(response){ 148 | console.log(response); // !dlrow olleH 149 | }); 150 | 151 | job.on("fail", function(){ 152 | console.log("Job failed :S"); 153 | }); 154 | 155 | ### Create a worker function 156 | 157 | gearnode.addFunction(func_name[, encoding], worker_func) 158 | 159 | Where 160 | 161 | * **func_name** is the name of the function to be created 162 | * **endocing** is the input encoding (default is buffer) 163 | * **worker_func** is the actual worker function 164 | 165 | #### Worker function 166 | 167 | worker_func = function(payload, job) 168 | 169 | Where 170 | 171 | * **payload** is the data sent by the client and in the encoding specified with *addFunction* 172 | * **job** is a Gearman Job object that can be used to send data back 173 | 174 | #### Worker Job object 175 | 176 | Worker Job object has the following methods 177 | 178 | * **complete(response)** - send the result of the function back to the client 179 | * **error(error)** - throw an exception (and end the job with *failed* status) 180 | * **fail()** - end the function without response data when the function failed 181 | * **warning(warning)** - send a warning message to the client 182 | * **data(response)** - send a partial response data to the client 183 | * **setStatus(numerator, denominator)** - send a progress event to the client 184 | 185 | #### Example 186 | 187 | var Gearnode = require("gearnode"); 188 | 189 | var worker = new Gearnode(); 190 | worker.addServer(); 191 | 192 | worker.addFunction("sqr", "number", function(payload, job){ 193 | if(payload < 0){ 194 | job.warning("Used number is smaller than zero!"); 195 | } 196 | job.complete(payload * payload); 197 | }); 198 | 199 | ### Detect connection errors 200 | 201 | When the connection is lost a "disconnect" event is emitted to the client/worker 202 | 203 | worker.addServer("gearman.lan"); 204 | worker.on("disconnect", function(server){ 205 | console.log("Connection lost from "+server_name); 206 | }); 207 | 208 | ## License 209 | 210 | MIT 211 | -------------------------------------------------------------------------------- /lib/gearnode.js: -------------------------------------------------------------------------------- 1 | var GearmanConnection = require("./gearman-connection"), 2 | EventEmitter = require('events').EventEmitter, 3 | utillib = require("util"); 4 | 5 | /** 6 | * new Gearnode() 7 | * 8 | * Creates a Gearnode object which can be used as a Gearman worker 9 | * or a client. This is an event emitter. 10 | **/ 11 | function Gearnode(){ 12 | EventEmitter.call(this); 13 | 14 | this.server_names = []; 15 | this.servers = {}; 16 | 17 | this.function_names = []; 18 | this.functions = {}; 19 | 20 | this.workerId = null; 21 | } 22 | utillib.inherits(Gearnode, EventEmitter); 23 | 24 | /** 25 | * Gearnode#addServer([server_name][, server_port]]) -> undefined 26 | * - server_name (String): hostname of the server, defaults to localhost 27 | * - server_port (Number): port for the Gearman server, defaults to 4730 28 | * 29 | * Adds a Gearman server to the list. If this instance is used as a worker, 30 | * then all connected servers can assign jobs. If this instance is a client, 31 | * only the last one from the list is used. 32 | **/ 33 | Gearnode.prototype.addServer = function(server_name, server_port){ 34 | server_name = (server_name || "127.0.0.1").toLowerCase().trim(); 35 | server_port = server_port || 4730; 36 | 37 | if(this.servers[server_name]){ 38 | return; 39 | } 40 | 41 | this.server_names.push(server_name); 42 | this.servers[server_name] = { 43 | name: server_name, 44 | port: server_port, 45 | connection: new GearmanConnection(server_name, server_port), 46 | functions: [] 47 | }; 48 | 49 | this.setupServerListeners(server_name); 50 | 51 | this.update(server_name); 52 | }; 53 | 54 | /** 55 | * Gearnode#removeServer(server_name) -> undefined 56 | * - server_name (String): hostname of the Gearman server 57 | * 58 | * Removes a server from the list of servers. 59 | **/ 60 | Gearnode.prototype.removeServer = function(server_name){ 61 | var connection, pos; 62 | 63 | server_name = (server_name || "127.0.0.1").toLowerCase().trim(); 64 | 65 | if(!this.servers[server_name]){ 66 | return false; 67 | } 68 | 69 | connection = this.servers[server_name].connection; 70 | connection.closeConnection(); 71 | 72 | if((pos = this.server_names.indexOf(server_name))>=0){ 73 | this.server_names.splice(pos, 1); 74 | } 75 | 76 | delete this.servers[server_name]; 77 | 78 | return true; 79 | }; 80 | 81 | /** 82 | * Gearnode#end() -> undefined 83 | * 84 | * Removes all listed servers 85 | **/ 86 | Gearnode.prototype.end = function(){ 87 | for(var i=this.server_names.length-1; i>=0; i--){ 88 | this.removeServer(this.server_names[i]); 89 | } 90 | }; 91 | 92 | /** 93 | * Gearnode#setupServerListeners(server_name) -> undefined 94 | * - server_name (String): hostname of the Gearman server 95 | * 96 | * This function sets up a set of listener for different events related 97 | * with the Gearman-Connection server instance. 98 | **/ 99 | Gearnode.prototype.setupServerListeners = function(server_name){ 100 | 101 | this.servers[server_name].connection.on("error", function(err){ 102 | console.log("Error with "+server_name); 103 | console.log(err.stack); 104 | }); 105 | 106 | this.servers[server_name].connection.on("job", this.runJob.bind(this, server_name)); 107 | 108 | this.servers[server_name].connection.on("created", (function(handle, options){ 109 | if(options && options.job){ 110 | options.job.emit("created", handle); 111 | } 112 | }).bind(this)); 113 | 114 | this.servers[server_name].connection.on("complete", (function(handle, response, options){ 115 | if(options && options.job){ 116 | options.job.emit("complete", response); 117 | } 118 | }).bind(this)); 119 | 120 | this.servers[server_name].connection.on("exception", (function(handle, error, options){ 121 | if(options && options.job){ 122 | options.job.emit("exception", error); 123 | } 124 | }).bind(this)); 125 | 126 | this.servers[server_name].connection.on("warning", (function(handle, error, options){ 127 | if(options && options.job){ 128 | options.job.emit("warning", error); 129 | } 130 | }).bind(this)); 131 | 132 | this.servers[server_name].connection.on("data", (function(handle, error, options){ 133 | if(options && options.job){ 134 | options.job.emit("data", error); 135 | } 136 | }).bind(this)); 137 | 138 | this.servers[server_name].connection.on("status", (function(handle, numerator, denominator, options){ 139 | if(options && options.job){ 140 | options.job.emit("status", numerator, denominator); 141 | } 142 | }).bind(this)); 143 | 144 | this.servers[server_name].connection.on("fail", (function(handle, options){ 145 | if(options && options.job){ 146 | options.job.emit("fail"); 147 | } 148 | }).bind(this)); 149 | 150 | this.servers[server_name].connection.on("disconnect", (function(){ 151 | this.emit("disconnect", server_name); 152 | }).bind(this)); 153 | }; 154 | 155 | /** 156 | * Gearnode#update(server_name) -> undefined 157 | * - server_name (String): hostname of the Gearman server 158 | * 159 | * Makes sure that a newly added server gets all the functions that 160 | * are supported by this worker instance 161 | **/ 162 | Gearnode.prototype.update = function(server_name){ 163 | if(!server_name){ 164 | return; 165 | } 166 | 167 | this.function_names.forEach((function(func_name){ 168 | this.register(func_name, server_name); 169 | }).bind(this)); 170 | 171 | if(this.workerId){ 172 | this.setWorkerId(server_name, this.workerId); 173 | } 174 | }; 175 | 176 | /** 177 | * Gearnode#register(func_name[, server_name]) -> undefined 178 | * - func_name (String): function name to be listed with the server 179 | * - server_name (String): hostname of the Gearman server 180 | * 181 | * If server_name is set, adds a function to this server. If not set 182 | * adds the function to all servers 183 | **/ 184 | Gearnode.prototype.register = function(func_name, server_name){ 185 | if(this.servers[server_name]){ 186 | if(this.servers[server_name].functions.indexOf(func_name)<0){ 187 | this.servers[server_name].connection.addFunction(func_name); 188 | this.servers[server_name].functions.push(func_name); 189 | } 190 | }else{ 191 | this.server_names.forEach((function(server_name){ 192 | if(server_name){ 193 | this.register(func_name, server_name); 194 | } 195 | }).bind(this)); 196 | } 197 | }; 198 | 199 | /** 200 | * Gearnode#unregister(func_name[, server_name]) -> undefined 201 | * - func_name (String): function name to be removed from the server 202 | * - server_name (String): hostname of the Gearman server 203 | * 204 | * If server_name is set, removes support for a function from this server. 205 | * If not set, removes the function from all servers 206 | **/ 207 | Gearnode.prototype.unregister = function(func_name, server_name){ 208 | var pos; 209 | if(this.servers[server_name]){ 210 | if((pos = this.servers[server_name].functions.indexOf(func_name))>=0){ 211 | this.servers[server_name].connection.removeFunction(func_name); 212 | this.servers[server_name].functions.splice(pos, 1); 213 | } 214 | }else{ 215 | this.server_names.forEach((function(server_name){ 216 | if(server_name){ 217 | this.unregister(func_name, server_name); 218 | } 219 | }).bind(this)); 220 | } 221 | }; 222 | 223 | // WORKER FUNCTIONS 224 | 225 | /** 226 | * Gearnode#runJob(server_name, handle, func_name, payload[, uid]) -> undefined 227 | * - server_name (String): hostname of the Gearman server 228 | * - handle (String): unique handle ID for the job 229 | * - func_name (String): name of the function to be run 230 | * - payload: (Buffer | String): data sent by the client 231 | * - uid (String): unique id set by the client 232 | * 233 | * Initiated by "job" event from the Gearman-Connection. Executes the 234 | * worker function with payload and a GearmanWorker object. The latter is 235 | * used to report back when the job is completed (the job migh be async) 236 | **/ 237 | Gearnode.prototype.runJob = function(server_name, handle, func_name, payload, uid){ 238 | uid = uid || null; 239 | if(this.functions[func_name]){ 240 | 241 | var encoding = this.functions[func_name].encoding.toLowerCase() || "buffer"; 242 | 243 | switch(encoding){ 244 | case "utf-8": 245 | case "ascii": 246 | case "base64": 247 | payload = payload && payload.toString(encoding) || ""; 248 | break; 249 | case "number": 250 | payload = Number(payload && payload.toString("ascii") || "") || 0; 251 | break; 252 | //case "buffer": 253 | default: 254 | // keep buffer 255 | } 256 | 257 | var job = new Gearnode.GearmanWorker(handle, server_name, this); 258 | this.functions[func_name].func(payload, job); 259 | }else{ 260 | this.servers[server_name].connection.jobError(handle, "Function "+func_name+" not found"); 261 | } 262 | }; 263 | 264 | /** 265 | * Gearnode#setWorkerId([server_name], id) -> undeifned 266 | * - server_name (String): hostname of the Gearman server 267 | * - id (String): identifier for this worker instance 268 | * 269 | * Registers an ID for this worker instance, useful when monitoring Gearman. 270 | * If server_name is set, set the Id for this server, otherwise set it for all. 271 | **/ 272 | Gearnode.prototype.setWorkerId = function(server_name, id){ 273 | var pos; 274 | 275 | if(arguments.length<2){ 276 | id = server_name; 277 | server_name = null; 278 | } 279 | if(!id){ 280 | return false; 281 | } 282 | 283 | if(server_name){ 284 | if(this.servers[server_name]){ 285 | this.servers[server_name].connection.setWorkerId(id); 286 | } 287 | }else{ 288 | this.workerId = id; 289 | this.server_names.forEach((function(server_name){ 290 | if(server_name){ 291 | this.setWorkerId(server_name, id); 292 | } 293 | }).bind(this)); 294 | } 295 | }; 296 | 297 | /** 298 | * Gearnode#addFunction(name[, encoding], func) -> undefined 299 | * - name (String): name of the function 300 | * - encoding (String): encoding of the payload when run as a job 301 | * defaults to "buffer" 302 | * - func (Function): the actual function to be run 303 | * 304 | * Sets up a worker function. If a function with the same name already 305 | * exists, it is overwritten. 306 | **/ 307 | Gearnode.prototype.addFunction = function(name, encoding, func){ 308 | if(!name){ 309 | return false; 310 | } 311 | 312 | if(!func && typeof encoding=="function"){ 313 | func = encoding; 314 | encoding = null; 315 | }else if(typeof func != "function"){ 316 | return; 317 | } 318 | 319 | if(!(name in this.functions)){ 320 | this.functions[name] = { 321 | func: func, 322 | encoding: encoding || "buffer" 323 | }; 324 | this.function_names.push(name); 325 | this.register(name); 326 | }else{ 327 | this.functions[name] = { 328 | func: func, 329 | encoding: encoding || "buffer" 330 | }; 331 | } 332 | }; 333 | 334 | /** 335 | * Gearnode#removeFunction(name) -> undefined 336 | * - name (String): name of the function to be removed 337 | * 338 | * Removes a function from the available functions list 339 | **/ 340 | Gearnode.prototype.removeFunction = function(name){ 341 | var pos; 342 | 343 | if(!name){ 344 | return false; 345 | } 346 | 347 | if((pos = this.function_names.indexOf(name))>=0){ 348 | delete this.functions[name]; 349 | this.function_names.splice(pos, 1); 350 | this.unregister(name); 351 | } 352 | }; 353 | 354 | // CLIENT FUNCTIONS 355 | 356 | /** 357 | * Gearnode#getExceptions([server_name], callback) -> undefined 358 | * - server_name (String): hostname of the Gearman server 359 | * - callback (Function): function to be run when done 360 | * 361 | * Notifies the server that worker exceptions should be delivered to 362 | * the client (not delivered by default). If server_name is specified 363 | * notifies only this server, otherwise notifies all. 364 | **/ 365 | Gearnode.prototype.getExceptions = function(server_name, callback){ 366 | var pos; 367 | 368 | if(!callback && typeof server_name =="function"){ 369 | callback = server_name; 370 | server_name = null; 371 | } 372 | 373 | if(server_name){ 374 | if(this.servers[server_name]){ 375 | 376 | this.servers[server_name].connection.getExceptions((function(err, success){ 377 | if(callback){ 378 | return callback(err, success); 379 | } 380 | if(err){ 381 | console.log("Server "+server_name+" responded with error: "+(err.message || err)); 382 | }else{ 383 | console.log("Exceptions are followed from "+server_name); 384 | } 385 | }).bind(this)); 386 | } 387 | }else{ 388 | this.server_names.forEach((function(server_name){ 389 | if(server_name){ 390 | this.getExceptions(server_name, callback); 391 | } 392 | }).bind(this)); 393 | } 394 | }; 395 | 396 | /** 397 | * Gearnode#submitJob(func_name, payload[, options]) -> Object 398 | * - func_name (String): name of the function to run 399 | * - payload (String | Buffer): data to be sent as the payload 400 | * - options (Object): options param 401 | * 402 | * Initiates a job to be run by a worker. Returns GearmanJob object. 403 | **/ 404 | Gearnode.prototype.submitJob = function(func_name, payload, options){ 405 | if(!func_name){ 406 | return false; 407 | } 408 | 409 | if(!this.server_names.length){ 410 | throw new Error("No Gearman servers specified"); 411 | } 412 | 413 | var server = this.servers[this.server_names[this.server_names.length-1]]; 414 | 415 | return new Gearnode.GearmanJob(func_name, payload, options, server); 416 | }; 417 | 418 | 419 | // CLIENT JOB 420 | /** 421 | * new Gearnode.GearmanJob(func_name, payload, options, server) 422 | * - func_name (String): name of the function to run 423 | * - payload (String | Buffer): data to be sent as the payload 424 | * - options (Object): options param 425 | * - server (Objet): server object from the servers list 426 | * 427 | * Creates an event emitter object and submits the job to the server 428 | **/ 429 | Gearnode.GearmanJob = function(func_name, payload, options, server){ 430 | EventEmitter.call(this); 431 | 432 | options = options || {}; 433 | options.job = this; 434 | 435 | server.connection.submitJob(func_name, payload, options); 436 | }; 437 | utillib.inherits(Gearnode.GearmanJob, EventEmitter); 438 | 439 | // WORKER JOB 440 | 441 | Gearnode.GearmanWorker = function(handle, server_name, gm){ 442 | this.handle = handle; 443 | this.server_name = server_name; 444 | this.gm = gm; 445 | }; 446 | 447 | Gearnode.GearmanWorker.prototype.complete = function(response){ 448 | this.gm.servers[this.server_name].connection.jobComplete(this.handle, response); 449 | }; 450 | 451 | Gearnode.GearmanWorker.prototype.data = function(data){ 452 | this.gm.servers[this.server_name].connection.jobData(this.handle, data); 453 | }; 454 | 455 | Gearnode.GearmanWorker.prototype.warning = function(warning){ 456 | this.gm.servers[this.server_name].connection.jobWarning(this.handle, warning); 457 | }; 458 | 459 | Gearnode.GearmanWorker.prototype.fail = function(){ 460 | this.gm.servers[this.server_name].connection.jobFail(this.handle); 461 | }; 462 | 463 | Gearnode.GearmanWorker.prototype.error = function(error){ 464 | this.gm.servers[this.server_name].connection.jobError(this.handle, error); 465 | }; 466 | 467 | Gearnode.GearmanWorker.prototype.setStatus = function(numerator, denominator){ 468 | numerator = parseInt(numerator, 10) || 0; 469 | denominator = parseInt(denominator, 10) || 0; 470 | this.gm.servers[this.server_name].connection.jobStatus(this.handle, numerator, denominator); 471 | }; 472 | 473 | module.exports = Gearnode; 474 | 475 | 476 | 477 | -------------------------------------------------------------------------------- /test/tests.js: -------------------------------------------------------------------------------- 1 | var Gearnode = require("../lib/gearnode"), 2 | GearmanConnection = require("../lib/gearman-connection"), 3 | testCase = require('nodeunit').testCase; 4 | 5 | 6 | exports["gearnode instance"] = function(test){ 7 | var gearman = new Gearnode(); 8 | test.expect(1); 9 | test.ok(gearman instanceof Gearnode, "Worker is a Gearnode instance"); 10 | test.done(); 11 | }; 12 | 13 | // ADD SERVER 14 | 15 | exports["test server"] = { 16 | 17 | "add one server": function(test){ 18 | var gearman = new Gearnode(); 19 | gearman.addServer(); 20 | 21 | test.expect(2); 22 | test.equal(gearman.server_names.length, 1, "One item in server_names array"); 23 | test.equal(Object.keys(gearman.servers).length, 1, "One item in gearman.servers object"); 24 | test.done(); 25 | }, 26 | 27 | "add one server multiple times": function(test){ 28 | var gearman = new Gearnode(); 29 | gearman.addServer("localhost"); 30 | gearman.addServer("localhost"); 31 | 32 | test.expect(2); 33 | test.equal(gearman.server_names.length, 1, "One item in server_names array"); 34 | test.equal(Object.keys(gearman.servers).length, 1, "One item in gearman.servers object"); 35 | test.done(); 36 | }, 37 | 38 | "add multiple servers": function(test){ 39 | var gearman = new Gearnode(); 40 | gearman.addServer("localhost.local"); 41 | gearman.addServer("localhost.lan"); 42 | 43 | test.expect(2); 44 | test.equal(gearman.server_names.length, 2, "Two items in server_names array"); 45 | test.equal(Object.keys(gearman.servers).length, 2, "Two items in gearman.servers object"); 46 | test.done(); 47 | }, 48 | 49 | "server instance": function(test){ 50 | var gearman = new Gearnode(); 51 | gearman.addServer(); 52 | 53 | test.expect(1); 54 | test.ok(gearman.servers[gearman.server_names[0]].connection instanceof GearmanConnection, "Connection instance"); 55 | test.done(); 56 | } 57 | }; 58 | 59 | // ADD FUNCTIONS 60 | 61 | exports["test functions"] = { 62 | 63 | "add one function": function(test){ 64 | var gearman = new Gearnode(); 65 | 66 | gearman.addFunction("foo", function(){}); 67 | 68 | test.expect(2); 69 | test.equal(gearman.function_names.length, 1, "One item in function_names array"); 70 | test.equal(Object.keys(gearman.functions).length, 1, "One item in gearman.functions object"); 71 | test.done(); 72 | }, 73 | 74 | "add one function multiple times": function(test){ 75 | var gearman = new Gearnode(); 76 | 77 | gearman.addFunction("foo", function(){}); 78 | gearman.addFunction("foo", function(){}); 79 | 80 | test.expect(2); 81 | test.equal(gearman.function_names.length, 1, "One item in function_names array"); 82 | test.equal(Object.keys(gearman.functions).length, 1, "One item in gearman.functions object"); 83 | test.done(); 84 | }, 85 | 86 | "add multiple functions": function(test){ 87 | var gearman = new Gearnode(); 88 | 89 | gearman.addFunction("foo", function(){}); 90 | gearman.addFunction("bar", function(){}); 91 | 92 | test.expect(2); 93 | test.equal(gearman.function_names.length, 2, "Two items in function_names array"); 94 | test.equal(Object.keys(gearman.functions).length, 2, "Two items in gearman.functions object"); 95 | test.done(); 96 | }, 97 | 98 | "function properties": function(test){ 99 | var gearman = new Gearnode(); 100 | 101 | gearman.addFunction("foo", function(){}); 102 | gearman.addFunction("bar", "string", function(){}); 103 | 104 | test.expect(6); 105 | test.equal(gearman.function_names[0], "foo", "Function name for foo"); 106 | test.equal(gearman.function_names[1], "bar", "Function name for bar"); 107 | test.equal(typeof gearman.functions[gearman.function_names[0]].func, "function", "Function instance for foo"); 108 | test.equal(typeof gearman.functions[gearman.function_names[1]].func, "function", "Function instance for bar"); 109 | test.equal(gearman.functions[gearman.function_names[0]].encoding, "buffer", "Function encoding for foo"); 110 | test.equal(gearman.functions[gearman.function_names[1]].encoding, "string", "Function encoding for bar"); 111 | test.done(); 112 | } 113 | }; 114 | 115 | // FUNCTIONS AND SERVES 116 | 117 | exports["functions + servers"] = { 118 | 119 | "add function to existing server": function(test){ 120 | var gearman = new Gearnode(); 121 | 122 | gearman.addServer("foo"); 123 | gearman.addFunction("bar", function(){}); 124 | 125 | test.expect(1); 126 | test.equal(gearman.servers.foo.functions.length, 1, "One item in server functions array"); 127 | test.done(); 128 | }, 129 | 130 | "add function before server": function(test){ 131 | var gearman = new Gearnode(); 132 | 133 | gearman.addFunction("bar", function(){}); 134 | gearman.addServer("foo"); 135 | 136 | test.expect(1); 137 | test.equal(gearman.servers.foo.functions.length, 1, "One item in server functions array"); 138 | test.done(); 139 | }, 140 | 141 | "add function without server throws": function(test){ 142 | var gearman = new Gearnode(); 143 | 144 | gearman.addFunction("bar", function(){}); 145 | 146 | test.throws(function(){ 147 | gearman.submitJob("test","test"); 148 | }); 149 | 150 | test.done(); 151 | } 152 | }; 153 | 154 | // WORKER ID 155 | 156 | exports["worker id"] = { 157 | 158 | "set worker id": function(test){ 159 | var gearman = new Gearnode(); 160 | 161 | gearman.setWorkerId("bar"); 162 | 163 | test.expect(1); 164 | test.equal(gearman.workerId, "bar", "Worker ID"); 165 | test.done(); 166 | }, 167 | 168 | "set worker id to servers": function(test){ 169 | var gearman = new Gearnode(); 170 | 171 | gearman.addServer("foo"); 172 | gearman.addServer("bar"); 173 | 174 | gearman.setWorkerId("baz"); 175 | 176 | test.expect(2); 177 | test.equal(gearman.servers.foo.connection.workerId, "baz", "Worker ID"); 178 | test.equal(gearman.servers.bar.connection.workerId, "baz", "Worker ID"); 179 | test.done(); 180 | }, 181 | 182 | "set worker id before server": function(test){ 183 | var gearman = new Gearnode(); 184 | 185 | gearman.addServer("foo"); 186 | gearman.setWorkerId("baz"); 187 | gearman.addServer("bar"); 188 | 189 | test.expect(2); 190 | test.equal(gearman.servers.foo.connection.workerId, "baz", "Worker ID"); 191 | test.equal(gearman.servers.bar.connection.workerId, "baz", "Worker ID"); 192 | test.done(); 193 | } 194 | }; 195 | 196 | module.exports["worker behavior"] = testCase({ 197 | setUp: function (callback) { 198 | this.worker = new Gearnode(); 199 | this.worker.addServer("localhost",7003); 200 | 201 | this.client = new Gearnode(); 202 | this.client.addServer("localhost",7003); 203 | 204 | this.worker.addFunction("testjob_upper", function(payload, job){ 205 | job.complete(payload.toString("utf-8").toUpperCase()); 206 | }); 207 | 208 | this.worker.addFunction("testjob_reverse_binary", function(payload, job){ 209 | var data = new Buffer(payload.length); 210 | for(var i=0; i<=payload.length; i++){ 211 | data[payload.length-i-1] = payload[i]; 212 | } 213 | job.complete(data); 214 | }); 215 | 216 | this.worker.addFunction("testjob_upper_utf8","utf-8", function(payload, job){ 217 | job.complete(payload.toUpperCase()); 218 | }); 219 | 220 | this.worker.addFunction("testjob_upper_base64","base64", function(payload, job){ 221 | job.complete(new Buffer(payload, "base64").toString("utf-8").toUpperCase()); 222 | }); 223 | 224 | this.worker.addFunction("testjob_getexception",function(payload, job){ 225 | job.error(new Error("Error happened")); 226 | }); 227 | 228 | this.worker.addFunction("testjob_partial",function(payload, job){ 229 | for(var i=0; i<4; i++){ 230 | job.data("data" + i); 231 | } 232 | job.complete("ready"); 233 | }); 234 | 235 | this.worker.addFunction("testjob_status",function(payload, job){ 236 | var total = 200; 237 | for(var i=5; i>0; i--){ 238 | job.setStatus(total/i, total); 239 | } 240 | job.complete("ready"); 241 | }); 242 | 243 | this.worker.addFunction("testjob_getwarning",function(payload, job){ 244 | job.warning("foo"); 245 | job.complete("bar"); 246 | }); 247 | 248 | this.worker.addFunction("testjob_getfail",function(payload, job){ 249 | job.fail(); 250 | }); 251 | 252 | this.worker.addFunction("testjob_disconnect",function(payload, job){ 253 | setTimeout(function(){ 254 | job.complete("bar"); 255 | },1000); 256 | }); 257 | 258 | callback(); 259 | }, 260 | 261 | tearDown: function (callback) { 262 | // clean up 263 | callback(); 264 | }, 265 | 266 | "submit job": function (test) { 267 | 268 | test.expect(1); 269 | 270 | var job = this.client.submitJob("testjob_upper","test"); 271 | 272 | job.on("complete", function(data){ 273 | test.equal(data.toString("utf-8"), "TEST", "Function success"); 274 | test.done(); 275 | }); 276 | 277 | job.on("fail", function(){ 278 | test.ok(false, "Function failed"); 279 | test.done(); 280 | }); 281 | job.on("error", function(){ 282 | test.ok(false, "Function failed with error"); 283 | test.done(); 284 | }); 285 | }, 286 | 287 | "submit job, send/receive binary": function (test) { 288 | 289 | test.expect(1); 290 | 291 | var data = new Buffer(256); 292 | for(var i=0; i<256; i++){ 293 | data[i] = i; 294 | } 295 | 296 | var job = this.client.submitJob("testjob_reverse_binary", data); 297 | 298 | job.on("complete", function(buf){ 299 | var ok = true; 300 | for(var i=0; i<=buf.length; i++){ 301 | if(data[buf.length-i-1] != buf[i]){ 302 | ok = false; 303 | break; 304 | } 305 | } 306 | test.ok(ok, "Received reversed binary"); 307 | test.done(); 308 | }); 309 | 310 | job.on("fail", function(){ 311 | test.ok(false, "Function failed"); 312 | test.done(); 313 | }); 314 | job.on("error", function(){ 315 | test.ok(false, "Function failed with error"); 316 | test.done(); 317 | }); 318 | }, 319 | 320 | "submit job, send payload utf-8": function (test) { 321 | 322 | test.expect(1); 323 | 324 | var job = this.client.submitJob("testjob_upper_utf8","test"); 325 | 326 | job.on("complete", function(data){ 327 | test.equal(data.toString("utf-8"), "TEST", "Function success"); 328 | test.done(); 329 | }); 330 | 331 | job.on("fail", function(){ 332 | test.ok(false, "Function failed"); 333 | test.done(); 334 | }); 335 | job.on("error", function(){ 336 | test.ok(false, "Function failed with error"); 337 | test.done(); 338 | }); 339 | }, 340 | 341 | "submit job, send payload base64": function (test) { 342 | 343 | test.expect(1); 344 | 345 | var job = this.client.submitJob("testjob_upper_base64","test"); 346 | 347 | job.on("complete", function(data){ 348 | test.equal(data.toString("utf-8"), "TEST", "Function success"); 349 | test.done(); 350 | }); 351 | 352 | job.on("fail", function(){ 353 | test.ok(false, "Function failed"); 354 | test.done(); 355 | }); 356 | job.on("error", function(){ 357 | test.ok(false, "Function failed with error"); 358 | test.done(); 359 | }); 360 | }, 361 | 362 | "submit job, expect utf-8 response": function (test) { 363 | 364 | test.expect(1); 365 | 366 | var job = this.client.submitJob("testjob_upper","test", {encoding:"utf-8"}); 367 | 368 | job.on("complete", function(data){ 369 | test.equal(data, "TEST", "Function success"); 370 | test.done(); 371 | }); 372 | 373 | job.on("fail", function(){ 374 | test.ok(false, "Function failed"); 375 | test.done(); 376 | }); 377 | job.on("error", function(){ 378 | test.ok(false, "Function failed with error"); 379 | test.done(); 380 | }); 381 | }, 382 | 383 | "submit job, expect base64 response": function (test) { 384 | 385 | test.expect(1); 386 | 387 | var job = this.client.submitJob("testjob_upper","test", {encoding:"base64"}); 388 | 389 | job.on("complete", function(data){ 390 | test.equal(data, new Buffer("TEST","utf-8").toString("base64"), "Function success"); 391 | test.done(); 392 | }); 393 | 394 | job.on("fail", function(){ 395 | test.ok(false, "Function failed"); 396 | test.done(); 397 | }); 398 | job.on("error", function(){ 399 | test.ok(false, "Function failed with error"); 400 | test.done(); 401 | }); 402 | }, 403 | 404 | "subscribe for exceptions": function(test){ 405 | test.expect(1); 406 | this.client.getExceptions((function(err, success){ 407 | test.ok(success,"Listening for exceptions"); 408 | test.done(); 409 | }).bind(this)); 410 | }, 411 | 412 | "fail": function (test) { 413 | 414 | test.expect(1); 415 | 416 | var job = this.client.submitJob("testjob_getfail","test", {encoding:"utf-8"}); 417 | 418 | job.on("complete", function(data){ 419 | test.ok(false, "Should not complete"); 420 | test.done(); 421 | }); 422 | 423 | job.on("fail", function(){ 424 | test.ok(true, "Function failed"); 425 | test.done(); 426 | }); 427 | 428 | job.on("error", function(){ 429 | test.ok(false, "Function failed with error"); 430 | test.done(); 431 | }); 432 | }, 433 | 434 | "partial data": function(test){ 435 | test.expect(5); 436 | 437 | var job = this.client.submitJob("testjob_partial", "test", {encoding:"utf-8"}), 438 | i = 0; 439 | 440 | job.on("complete", function(data){ 441 | test.equal(data, "ready", "Function success"); 442 | test.done(); 443 | }); 444 | 445 | job.on("fail", function(){ 446 | test.ok(false, "Function failed"); 447 | test.done(); 448 | }); 449 | 450 | job.on("error", function(){ 451 | test.ok(false, "Function failed with error"); 452 | test.done(); 453 | }); 454 | 455 | job.on("data", function(data){ 456 | test.equal("data" + (i++), data, "Function part OK"); 457 | }); 458 | }, 459 | 460 | "status updates": function(test){ 461 | test.expect(11); 462 | 463 | var job = this.client.submitJob("testjob_status", "test", {encoding:"utf-8"}), 464 | i = 0, data = [40, 50, 66, 100, 200], total=200; 465 | 466 | job.on("complete", function(data){ 467 | test.equal(data, "ready", "Function success"); 468 | test.done(); 469 | }); 470 | 471 | job.on("fail", function(){ 472 | test.ok(false, "Function failed"); 473 | test.done(); 474 | }); 475 | 476 | job.on("error", function(){ 477 | test.ok(false, "Function failed with error"); 478 | test.done(); 479 | }); 480 | 481 | job.on("status", function(numerator, denominator){ 482 | test.equal(data[i++], numerator, "Progress data"); 483 | test.equal(total, denominator, "Progress total"); 484 | }); 485 | }, 486 | 487 | "warning": function (test) { 488 | 489 | test.expect(2); 490 | 491 | var job = this.client.submitJob("testjob_getwarning","test", {encoding:"utf-8"}); 492 | 493 | job.on("complete", function(data){ 494 | test.equal(data, "bar", "Completed"); 495 | test.done(); 496 | }); 497 | 498 | job.on("warning", function(data){ 499 | test.equal(data, "foo", "Function warning"); 500 | }); 501 | 502 | job.on("fail", function(){ 503 | test.ok(false, "Function failed"); 504 | test.done(); 505 | }); 506 | 507 | job.on("error", function(){ 508 | test.ok(false, "Function failed with error"); 509 | test.done(); 510 | }); 511 | }, 512 | 513 | "disconnect server": function (test) { 514 | 515 | test.expect(1); 516 | 517 | var job = this.client.submitJob("testjob_disconnect","test"); 518 | 519 | job.on("complete", function(data){ 520 | test.ok(false, "Should not complete"); 521 | test.done(); 522 | }); 523 | 524 | job.on("fail", function(){ 525 | test.ok(true, "Function failed"); 526 | test.done(); 527 | }); 528 | 529 | job.on("error", function(){ 530 | test.ok(false, "Function failed with error"); 531 | test.done(); 532 | }); 533 | 534 | setTimeout((function(){ 535 | this.client.servers[this.client.server_names[this.client.server_names.length-1]].connection.close(); 536 | }).bind(this), 100); 537 | }, 538 | 539 | "disconnect event": function (test) { 540 | 541 | test.expect(2); 542 | 543 | var job = this.client.submitJob("testjob_disconnect","test"); 544 | 545 | this.client.on("disconnect", function(server_name){ 546 | test.equal(server_name, "localhost", "Server disconnected"); 547 | test.done(); 548 | }); 549 | 550 | job.on("complete", function(data){ 551 | test.ok(false, "Should not complete"); 552 | test.done(); 553 | }); 554 | 555 | job.on("fail", function(){ 556 | test.ok(true, "Function failed"); 557 | //test.done(); 558 | }); 559 | 560 | job.on("error", function(){ 561 | test.ok(false, "Function failed with error"); 562 | test.done(); 563 | }); 564 | 565 | setTimeout((function(){ 566 | this.client.servers[this.client.server_names[this.client.server_names.length-1]].connection.close(); 567 | }).bind(this), 100); 568 | } 569 | }); 570 | -------------------------------------------------------------------------------- /lib/gearman-connection.js: -------------------------------------------------------------------------------- 1 | var netlib = require("net"), 2 | tools = require("./tools"), 3 | EventEmitter = require('events').EventEmitter, 4 | utillib = require("util"); 5 | 6 | module.exports = GearmanConnection; 7 | 8 | function GearmanConnection(server, port){ 9 | EventEmitter.call(this); 10 | 11 | this.server = server; 12 | this.port = port; 13 | 14 | this.command_queue = []; 15 | this.queue_pipe = []; 16 | 17 | this.queued_jobs = {}; 18 | 19 | this.workerId = null; 20 | 21 | this.connected = false; 22 | this.processing = false; 23 | this.failed = false; 24 | 25 | this.remainder = false; 26 | 27 | this.debug = false; 28 | } 29 | utillib.inherits(GearmanConnection, EventEmitter); 30 | 31 | GearmanConnection.packet_types = { 32 | CAN_DO: 1, 33 | CANT_DO: 2, 34 | RESET_ABILITIES: 3, 35 | PRE_SLEEP: 4, 36 | NOOP: 6, 37 | SUBMIT_JOB: 7, 38 | JOB_CREATED: 8, 39 | GRAB_JOB: 9, 40 | NO_JOB: 10, 41 | JOB_ASSIGN: 11, 42 | WORK_STATUS: 12, 43 | WORK_COMPLETE: 13, 44 | WORK_FAIL: 14, 45 | GET_STATUS: 15, 46 | ECHO_REQ: 16, 47 | ECHO_RES: 17, 48 | SUBMIT_JOB_BG: 18, 49 | ERROR: 19, 50 | STATUS_RES: 20, 51 | SUBMIT_JOB_HIGH: 21, 52 | SET_CLIENT_ID: 22, 53 | CAN_DO_TIMEOUT: 23, 54 | ALL_YOURS: 24, 55 | WORK_EXCEPTION: 25, 56 | OPTION_REQ: 26, 57 | OPTION_RES: 27, 58 | WORK_DATA: 28, 59 | WORK_WARNING: 29, 60 | GRAB_JOB_UNIQ: 30, 61 | JOB_ASSIGN_UNIQ: 31, 62 | SUBMIT_JOB_HIGH_BG: 32, 63 | SUBMIT_JOB_LOW: 33, 64 | SUBMIT_JOB_LOW_BG: 34, 65 | SUBMIT_JOB_SCHED: 35, 66 | SUBMIT_JOB_EPOCH: 36 67 | }; 68 | 69 | GearmanConnection.packet_types_reversed = { 70 | "1": "CAN_DO", 71 | "2": "CANT_DO", 72 | "3": "RESET_ABILITIES", 73 | "4": "PRE_SLEEP", 74 | "6": "NOOP", 75 | "7": "SUBMIT_JOB", 76 | "8": "JOB_CREATED", 77 | "9": "GRAB_JOB", 78 | "10": "NO_JOB", 79 | "11": "JOB_ASSIGN", 80 | "12": "WORK_STATUS", 81 | "13": "WORK_COMPLETE", 82 | "14": "WORK_FAIL", 83 | "15": "GET_STATUS", 84 | "16": "ECHO_REQ", 85 | "17": "ECHO_RES", 86 | "18": "SUBMIT_JOB_BG", 87 | "19": "ERROR", 88 | "20": "STATUS_RES", 89 | "21": "SUBMIT_JOB_HIGH", 90 | "22": "SET_CLIENT_ID", 91 | "23": "CAN_DO_TIMEOUT", 92 | "24": "ALL_YOURS", 93 | "25": "WORK_EXCEPTION", 94 | "26": "OPTION_REQ", 95 | "27": "OPTION_RES", 96 | "28": "WORK_DATA", 97 | "29": "WORK_WARNING", 98 | "30": "GRAB_JOB_UNIQ", 99 | "31": "JOB_ASSIGN_UNIQ", 100 | "32": "SUBMIT_JOB_HIGH_BG", 101 | "33": "SUBMIT_JOB_LOW", 102 | "34": "SUBMIT_JOB_LOW_BG", 103 | "35": "SUBMIT_JOB_SCHED", 104 | "36": "SUBMIT_JOB_EPOCH" 105 | }; 106 | 107 | GearmanConnection.param_count = { 108 | ERROR: ["string","string"], 109 | JOB_ASSIGN: ["string","string", "buffer"], 110 | JOB_ASSIGN_UNIQ: ["string","string", "string", "buffer"], 111 | JOB_CREATED: ["string", "string"], 112 | WORK_COMPLETE: ["string", "buffer"], 113 | WORK_EXCEPTION: ["string", "string"], 114 | WORK_WARNING: ["string", "string"], 115 | WORK_DATA: ["string", "buffer"], 116 | WORK_FAIL: ["string"], 117 | WORK_STATUS: ["string", "number", "number"] 118 | }; 119 | 120 | GearmanConnection.prototype.sendCommand = function(command){ 121 | if(!command){ 122 | return false; 123 | } 124 | 125 | if(typeof command == "string"){ 126 | command = { 127 | type: command 128 | }; 129 | } 130 | 131 | if(!command.params){ 132 | command.params = []; 133 | } 134 | this.command_queue.push(command); 135 | this.processQueue(); 136 | }; 137 | 138 | 139 | GearmanConnection.prototype.processQueue = function(){ 140 | var command; 141 | 142 | // if no connection yet, open one 143 | if(!this.connected){ 144 | return this.connect(); 145 | } 146 | 147 | // get commands as FIFO 148 | if(this.command_queue.length){ 149 | this.processing = true; 150 | command = this.command_queue.shift(); 151 | process.nextTick(this.runCommand.bind(this, command)); 152 | }else{ 153 | this.processing = false; 154 | } 155 | }; 156 | 157 | GearmanConnection.prototype.runCommand = function(command){ 158 | if(!command || !command.type || !GearmanConnection.packet_types[command.type]){ 159 | return; 160 | } 161 | this.send(command); 162 | }; 163 | 164 | GearmanConnection.prototype.send = function(command){ 165 | var magicREQ = new Buffer([0, 82, 69, 81]), //\0REQ 166 | type = tools.packInt(GearmanConnection.packet_types[command.type], 4), 167 | param, params = [], paramlen = 0, size = 0, buf, pos, 168 | i, len; 169 | 170 | // teeme parameetritest ükshaaval Buffer objektid ning loeme pikkused kokku 171 | for(i=0, len=command.params.length; i1){ 184 | size += params.length - 1; 185 | } 186 | 187 | paramlen = tools.packInt(size, 4); 188 | 189 | // add the length for \0REQ 4B + type 4B + paramsize 4B 190 | size += 12; 191 | 192 | // loome objekti mis saata serverile 193 | buf = new Buffer(size); 194 | 195 | // kopeerime Magick baidid 196 | magicREQ.copy(buf, 0, 0, 4); 197 | 198 | // kopeerime käsu koodi 199 | type.copy(buf, 4, 0); 200 | 201 | // kopeerime parameetrite pikkuse 4B 202 | paramlen.copy(buf, 8, 0); 203 | 204 | // parameetrite jaoks on stardipositsioon 12s bait 205 | pos = 12; 206 | 207 | // kopeerime ükshaaval parameetrid 208 | for(i=0, len=params.length; i outgoing"); 225 | console.log(command); 226 | console.log(buf); 227 | } 228 | 229 | // saada teele 230 | process.nextTick((function(){ 231 | this.socket.write(buf, (function(){ 232 | // kui saadetud, käivita järgmine 233 | // TODO: selle võiks ehk välja tõsta, käsud saab korraga saata 234 | if(this.debug){ 235 | console.log("--> data sent"); 236 | } 237 | this.processQueue(); 238 | }).bind(this)); 239 | }).bind(this)); 240 | }; 241 | 242 | // CONNECTION COMMANDS 243 | 244 | GearmanConnection.prototype.connect = function(){ 245 | 246 | if(this.connected || this.connecting){ 247 | // juhul kui ühendus on juba olemas käivita protsessimine 248 | if(this.connected && !this.processing){ 249 | this.processQueue(); 250 | } 251 | return false; 252 | } 253 | 254 | this.connecting = true; 255 | 256 | if(this.debug){ 257 | console.log("connecting..."); 258 | } 259 | this.socket = netlib.createConnection(this.port, this.server); 260 | 261 | this.socket.on("connect", (function(){ 262 | this.connecting = false; 263 | this.connected = true; 264 | 265 | if(this.debug){ 266 | console.log("connected!"); 267 | } 268 | 269 | this.processQueue(); 270 | }).bind(this)); 271 | 272 | this.socket.on("end", this.close.bind(this)); 273 | this.socket.on("error", this.close.bind(this)); 274 | this.socket.on("close", this.close.bind(this)); 275 | this.socket.on("timeout", this.close.bind(this)); 276 | 277 | this.socket.on("data", this.receive.bind(this)); 278 | }; 279 | 280 | GearmanConnection.prototype.closeConnection = function(){ 281 | if(this.connected){ 282 | this.socket.end(); 283 | } 284 | }; 285 | 286 | GearmanConnection.prototype.close = function(){ 287 | if(this.connected){ 288 | if(this.socket){ 289 | try{ 290 | this.socket.end(); 291 | }catch(E){} 292 | } 293 | this.connected = false; 294 | this.connecting = false; 295 | 296 | // kill all pending jobs 297 | var handles = Object.keys(this.queued_jobs), original; 298 | 299 | for(var i=0, len = handles.length; i 12+paramlen){ 499 | this.remainder = new Buffer(buf.length - (12+paramlen)); 500 | buf.copy(this.remainder, 0, 12+paramlen); 501 | process.nextTick(this.receive.bind(this)); 502 | }else{ 503 | this.remainder = false; 504 | } 505 | 506 | if(this.debug){ 507 | console.log("<-- incoming"); 508 | console.log(type, params); 509 | } 510 | 511 | this.handleCommand(type, params, piped); 512 | }; 513 | 514 | GearmanConnection.prototype.handleCommand = function(type, paramsBuffer, command){ 515 | 516 | var params = [], hint, positions = [], curpos=0, curparam, i, len; 517 | 518 | // check if there are expected params and if so, break 519 | // the buffer into individual pieces 520 | if((hint = GearmanConnection.param_count[type]) && hint.length){ 521 | 522 | // find \0 positions for individual params 523 | for(i=0, len = paramsBuffer.length; i= hint.length-1){ 527 | break; 528 | } 529 | } 530 | } 531 | 532 | for(i=0, len = positions.length + 1; i