├── LICENSE.txt ├── README.md ├── Tests ├── exampleCommandFormatting.m ├── exampleListener.m ├── exitHandler.m └── testConstructor.m ├── processManager.m └── processState.m /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Brian Lau 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in 12 | the documentation and/or other materials provided with the distribution 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 18 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # processManager 2 | 3 | A Matlab class for launching and managing processes that run asynchronously from the main Matlab process. This can already be done with something like `system('dir &');` but processManager makes it easy to: 4 | 5 | * launch and manage multiple processes 6 | * check on the progress of running processes 7 | * capture & display `stdout` and `stderr` streams of each process 8 | * issue event notifications when processes finish 9 | 10 | while allowing you to continue working in the main Matlab process. 11 | 12 | Some toy examples are illustrated below and in the [wiki](https://github.com/brian-lau/MatlabProcessManager/wiki). A more elaborate application is a [Matlab interface](https://github.com/brian-lau/MatlabStan) that does MCMC sampling using [Stan](http://mc-stan.org/). 13 | 14 | ## Installation & Examples 15 | Download [processManager](https://github.com/brian-lau/MatlabProcessManager/archive/master.zip), add the m-file to your Matlab path, and you're ready to go. 16 | 17 | processManager was developed and tested on OSX with Matlab 2012a, but should work on all platforms that Matlab supports, so long as it is running >=R2008a (for handle objects) with JDK >=1.1 (this will always be true unless you changed the default JDK). 18 | 19 | ### Optional 20 | Installing Steve Eddins's [linewrap](http://www.mathworks.com/matlabcentral/fileexchange/9909-line-wrap-a-string) function is useful for dealing with unwrapped messages. His [xUnit test framework](http://www.mathworks.com/matlabcentral/fileexchange/22846-matlab-xunit-test-framework) is required if you want to run the unit tests. 21 | 22 | ### Examples 23 | 24 | #### Running a simple command 25 | ``` 26 | p = processManager('command','nslookup www.google.com'); 27 | ``` 28 | 29 | #### Command with ongoing output 30 | ``` 31 | p = processManager('command','ping www.google.com'); 32 | 33 | % To keep the process running silently, 34 | p.printStdout = false; 35 | 36 | % ... Check process status 37 | p.check(); 38 | 39 | % When you want to see the io stream again 40 | p.printStdout = true; 41 | 42 | % Terminate 43 | p.stop(); 44 | ``` 45 | 46 | #### Multiples processes using object arrays 47 | You can pack multiple processes into an object array for easy management. 48 | ``` 49 | p(1) = processManager('id','google','command','ping www.google.com','autoStart',false); 50 | p(2) = processManager('id','yahoo','command','ping www.yahoo.com','autoStart',false); 51 | p.start(); 52 | 53 | % Tired of hearing about second process 54 | p(2).printStdout = false; 55 | 56 | % You can check the status of individual processes 57 | p(2).check(); 58 | 59 | % Or all processes 60 | p.check() 61 | 62 | % ... if you want to hear about an individual process later, 63 | p(2).printStdout = true; 64 | 65 | % Terminate all processes 66 | p.stop(); 67 | ``` 68 | 69 | ## Need help? 70 | You may be able to find a solution in the [wiki](https://github.com/brian-lau/MatlabProcessManager/wiki/Potential-gotchas). Otherwise, open an [issue](https://github.com/brian-lau/MatlabProcessManager/issues). 71 | 72 | Contributions 73 | -------------------------------- 74 | Copyright (c) 2017 Brian Lau [brian.lau@upmc.fr](mailto:brian.lau@upmc.fr), see [LICENSE](https://github.com/brian-lau/MatlabProcessManager/blob/master/LICENSE.txt) 75 | 76 | Please feel free to [fork](https://github.com/brian-lau/MatlabProcessManager/fork) and contribute! 77 | -------------------------------------------------------------------------------- /Tests/exampleCommandFormatting.m: -------------------------------------------------------------------------------- 1 | % This command will be parsed 2 | p = processManager('command','ls -la'); 3 | 4 | % This command will be passed without parsing 5 | p = processManager('command',java.lang.String('ls -la')); 6 | 7 | % Using an array allows spaces 8 | command = javaArray('java.lang.String',3); 9 | command(1) = java.lang.String('ls'); 10 | command(2) = java.lang.String('-la'); 11 | command(3) = java.lang.String('test space'); % Directory name with space 12 | p = processManager('command',command); 13 | 14 | % Using an array allows spaces (I copied ping into a directory with spaces) 15 | command = javaArray('java.lang.String',2); 16 | command(1) = java.lang.String('/Users/brian/Documents/Code/Repos/MatlabProcessManager/Tests/test space/ping'); 17 | command(2) = java.lang.String('www.google.com'); 18 | p = processManager('command',command); 19 | 20 | % This can also be done using a shell command 21 | command = javaArray('java.lang.String',2); 22 | command(1) = java.lang.String('sh'); 23 | command(2) = java.lang.String('-c'); 24 | command(3) = java.lang.String('ls -la test\ space'); 25 | p = processManager('command',command); 26 | 27 | % You can do without java by using a cell array 28 | p = processManager('command',{'sh' '-c' 'ls -la test\ space'}); -------------------------------------------------------------------------------- /Tests/exampleListener.m: -------------------------------------------------------------------------------- 1 | % A processManager object issues a notification when its process finishes. 2 | % Listening for this notification is as simple as attaching a listener and 3 | % defining a callback for the event. 4 | % 5 | % http://www.mathworks.com/help/matlab/matlab_oop/learning-to-use-events-and-listeners.html 6 | if ispc 7 | cmd = 'ping -n5 www.google.com'; 8 | else 9 | cmd = 'ping -c5 www.google.com'; 10 | end 11 | 12 | % Create an object and attach the listener 13 | p = processManager('id','ping','command',cmd); 14 | addlistener(p.state,'exit',@exitHandler); 15 | -------------------------------------------------------------------------------- /Tests/exitHandler.m: -------------------------------------------------------------------------------- 1 | function exitHandler(src,data) 2 | fprintf('\n'); 3 | fprintf('Listener notified!\n'); 4 | fprintf('Process %s exited with exitValue = %g\n',src.id,src.exitValue); 5 | fprintf('Event name %s\n',data.EventName); 6 | fprintf('\n'); 7 | end -------------------------------------------------------------------------------- /Tests/testConstructor.m: -------------------------------------------------------------------------------- 1 | % Requires the xUnit test framework 2 | % http://www.mathworks.com/matlabcentral/fileexchange/22846-matlab-xunit-test-framework 3 | 4 | function test_suite = testConstructor 5 | initTestSuite; 6 | 7 | function testNoArgs 8 | p = processManager(); 9 | assertEqual(p.running,false); 10 | assertEqual(p.exitValue,NaN); 11 | assertTrue(isa(p,'processManager'),'Constructor failed to create pointProcess without inputs'); 12 | 13 | function testArgs 14 | p = processManager('id',999,... 15 | 'command','ping www.google.com',... 16 | 'workingDir',tempdir,... 17 | 'envp',{'FOO=test'},... 18 | 'printStdout',false,... 19 | 'printStderr',false,... 20 | 'keepStdout',true,... 21 | 'keepStderr',true,... 22 | 'wrap',99,... 23 | 'autoStart',false,... 24 | 'verbose',true,... 25 | 'pollInterval',1 ... 26 | ); 27 | assertEqual(p.id,'999'); 28 | assertEqual(p.command,'ping www.google.com'); 29 | assertEqual(p.workingDir,tempdir); 30 | % 31 | assertEqual(p.printStdout,false); 32 | assertEqual(p.printStderr,false); 33 | assertEqual(p.keepStdout,true); 34 | assertEqual(p.keepStderr,true); 35 | assertEqual(p.wrap,99); 36 | assertEqual(p.autoStart,false); 37 | assertEqual(p.verbose,true); 38 | assertEqual(p.pollInterval,1); 39 | 40 | function testBadArgs 41 | f = @() processManager('id',{'should' 'not' 'work'}); 42 | assertExceptionThrown(f,'processManager:id:InputFormat'); 43 | 44 | f = @() processManager('command',1); 45 | assertExceptionThrown(f, 'processManager:command:InputFormat'); 46 | f = @() processManager('command',{1 2}); 47 | assertExceptionThrown(f, 'processManager:start:InputFormat'); 48 | f = @() processManager('command','thiscommanddoesnotexist'); 49 | assertExceptionThrown(f, 'processManager:start:InputFormat'); 50 | f = @() processManager('command',{'this' 'command' 'does' 'not' 'exist'}); 51 | assertExceptionThrown(f, 'processManager:start:InputFormat'); 52 | 53 | f = @() processManager('workingDir',1); 54 | assertExceptionThrown(f, 'processManager:workingDir:InputFormat'); 55 | f = @() processManager('workingDir','/this/directory/does/not/exist/'); 56 | assertExceptionThrown(f, 'processManager:workingDir:InputFormat'); 57 | 58 | f = @() processManager('printStderr',1); 59 | assertExceptionThrown(f, 'processManager:printStderr:InputFormat'); 60 | f = @() processManager('printStdout',1); 61 | assertExceptionThrown(f, 'processManager:printStdout:InputFormat'); 62 | f = @() processManager('printStderr','t'); 63 | assertExceptionThrown(f, 'processManager:printStderr:InputFormat'); 64 | f = @() processManager('printStdout','t'); 65 | assertExceptionThrown(f, 'processManager:printStdout:InputFormat'); 66 | f = @() processManager('printStderr',{true true}); 67 | assertExceptionThrown(f, 'processManager:printStderr:InputFormat'); 68 | f = @() processManager('printStdout',{true true}); 69 | assertExceptionThrown(f, 'processManager:printStdout:InputFormat'); 70 | 71 | f = @() processManager('keepStderr',1); 72 | assertExceptionThrown(f, 'processManager:keepStderr:InputFormat'); 73 | f = @() processManager('keepStdout',1); 74 | assertExceptionThrown(f, 'processManager:keepStdout:InputFormat'); 75 | f = @() processManager('keepStderr','t'); 76 | assertExceptionThrown(f, 'processManager:keepStderr:InputFormat'); 77 | f = @() processManager('keepStdout','t'); 78 | assertExceptionThrown(f, 'processManager:keepStdout:InputFormat'); 79 | f = @() processManager('keepStderr',{true true}); 80 | assertExceptionThrown(f, 'processManager:keepStderr:InputFormat'); 81 | f = @() processManager('keepStdout',{true true}); 82 | assertExceptionThrown(f, 'processManager:keepStdout:InputFormat'); 83 | 84 | f = @() processManager('wrap',0); 85 | assertExceptionThrown(f, 'processManager:wrap:InputFormat'); 86 | f = @() processManager('wrap',{100}); 87 | assertExceptionThrown(f, 'processManager:wrap:InputFormat'); 88 | f = @() processManager('wrap',[10 10]); 89 | assertExceptionThrown(f, 'processManager:wrap:InputFormat'); 90 | 91 | f = @() processManager('autoStart',0); 92 | assertExceptionThrown(f, 'processManager:autoStart:InputFormat'); 93 | f = @() processManager('autoStart','t'); 94 | assertExceptionThrown(f, 'processManager:autoStart:InputFormat'); 95 | f = @() processManager('autoStart',{0}); 96 | assertExceptionThrown(f, 'processManager:autoStart:InputFormat'); 97 | 98 | f = @() processManager('verbose',0); 99 | assertExceptionThrown(f, 'processManager:verbose:InputFormat'); 100 | f = @() processManager('verbose','true'); 101 | assertExceptionThrown(f, 'processManager:verbose:InputFormat'); 102 | f = @() processManager('verbose',[true true]); 103 | assertExceptionThrown(f, 'processManager:verbose:InputFormat'); 104 | 105 | f = @() processManager('pollInterval',0); 106 | assertExceptionThrown(f, 'processManager:pollInterval:InputFormat'); 107 | f = @() processManager('pollInterval','t'); 108 | assertExceptionThrown(f, 'processManager:pollInterval:InputFormat'); 109 | f = @() processManager('pollInterval',{0}); 110 | assertExceptionThrown(f, 'processManager:pollInterval:InputFormat'); 111 | 112 | -------------------------------------------------------------------------------- /processManager.m: -------------------------------------------------------------------------------- 1 | % PROCESSMANAGER - Launch and manage external processes 2 | % 3 | % obj = processManager(varargin); 4 | % 5 | % Class for launching and managing processes than run asynchronously 6 | % and in parallel to the main Matlab process. This could be done with 7 | % something like 8 | % 9 | % >> system('dir &'); 10 | % 11 | % but using processManager allows you to start and stop processes, peek 12 | % and check on the progress of running processes, all while allowing you 13 | % to continue working in the main Matlab process. 14 | % 15 | % All inputs are passed in using name/value pairs. The name is a string 16 | % followed by the value (described below). 17 | % The only required input is the command. 18 | % The order of the pairs does not matter, nor does the case. 19 | % 20 | % More information and can be found on GitHub: 21 | % https://github.com/brian-lau/MatlabProcessManager/wiki 22 | % 23 | % INPUTS 24 | % command - command to execute in separate process, can take the 25 | % form of 26 | % 1) string defining complete command including arguments 27 | % 2) cell array of strings, parsing the command and each 28 | % argument into a separate cell array element 29 | % 30 | % OPTIONAL 31 | % id - string identifier for process, default '' 32 | % workingDir - string defining working directory 33 | % envp - not working yet 34 | % printStdout - boolean to print stdout stream, default true 35 | % printStderr - boolean to print stderr stream, default true 36 | % wrap - number of columns for wrapping lines, default = 80 37 | % keepStdout - boolean to keep stdout stream, default false 38 | % keepStderr - boolean to keep stderr stream, default false 39 | % verbose - boolean to print processManager info, default false 40 | % autoStart - boolean to start process immediately, default true 41 | % pollInterval - double defining polling interval in sec, default 0.5 42 | % Take care with this variable, if set too long, one risks 43 | % permanently blocking Matlab when streams buffers are not 44 | % drained fast enough. If you don't want to see output, 45 | % it's safer to set printStdout and printStderr false 46 | % 47 | % METHODS 48 | % start - start process(es) 49 | % stop - stop process(es) 50 | % check - check running process(es) 51 | % block - block until done 52 | % 53 | % EXAMPLES 54 | % % 1) Running a simple command 55 | % p = processManager('command','nslookup www.google.com'); 56 | % 57 | % % 2) Command with ongoing output 58 | % p = processManager('command','ping www.google.com'); 59 | % % To keep the process running silently, 60 | % p.printStdout = false; 61 | % % ... Check back later 62 | % p.printStdout = true; 63 | % % Terminate 64 | % p.stop(); 65 | % 66 | % % 3) Multiples processes 67 | % p(1) = processManager('id','google','command','ping www.google.com','autoStart',false); 68 | % p(2) = processManager('id','yahoo','command','ping www.yahoo.com','autoStart',false); 69 | % p.start() 70 | % % Tired of hearing about second process 71 | % p(2).printStdout = false; 72 | % % ... if you want to hear back later, 73 | % p(2).printStdout = true; 74 | % p.stop(); 75 | % 76 | % $ Copyright (C) 2017 Brian Lau http://www.subcortex.net/ $ 77 | % Released under the BSD license. The license and most recent version 78 | % of the code can be found on GitHub: 79 | % https://github.com/brian-lau/MatlabProcessManager 80 | 81 | % TODO 82 | % o If streams are stored maybe we need to buffer a finite number of lines 83 | % o Generate unique names for each timer. check using timerfindall, storename 84 | % o cprintf for colored output for each process? 85 | 86 | classdef processManager < handle 87 | properties(SetAccess = public) 88 | id 89 | command 90 | envp 91 | workingDir 92 | 93 | printStderr 94 | printStdout 95 | wrap 96 | keepStderr 97 | keepStdout 98 | autoStart 99 | 100 | verbose 101 | pollInterval 102 | end 103 | properties(SetAccess = private) 104 | stderr = {}; 105 | stdout = {}; 106 | end 107 | properties(SetAccess = private, Dependent = true) 108 | running 109 | exitValue 110 | end 111 | properties(SetAccess = private, Hidden = true) 112 | process 113 | state % processState() object 114 | stderrReader 115 | stdoutReader 116 | pollTimer 117 | end 118 | properties(SetAccess = protected) 119 | version = '0.5.2'; 120 | end 121 | 122 | methods 123 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 124 | %% Constructor 125 | function self = processManager(varargin) 126 | % Constructor, arguments are taken as name/value pairs 127 | % 128 | % id - string identifier for process, default '' 129 | % command - string defining command to execute, required 130 | % workingDir - string defining working directory 131 | % envp - not working yet 132 | % printStdout - boolean to print stdout stream, default true 133 | % printStderr - boolean to print stderr stream, default true 134 | % wrap - number of columns for wrapping lines, default = 80 135 | % keepStdout - boolean to keep stdout stream, default false 136 | % keepStderr - boolean to keep stderr stream, default false 137 | % autoStart - boolean to start process immediately, default true 138 | % verbose - boolean to print processManager info, default false 139 | % pollInterval - double defining polling interval in sec, default 0.5 140 | % Take care with this variable, if set too long, 141 | % runs the risk of blocking Matlab when streams buffers 142 | % not drained fast enough 143 | % If you don't want to see output, better to set 144 | % printStdout and printStderr false 145 | % 146 | p = inputParser; 147 | p.KeepUnmatched = false; 148 | p.FunctionName = 'processManager constructor'; 149 | p.addParamValue('id',''); 150 | p.addParamValue('command',''); 151 | p.addParamValue('workingDir',''); 152 | p.addParamValue('envp',''); 153 | p.addParamValue('printStdout',true); 154 | p.addParamValue('printStderr',true); 155 | p.addParamValue('keepStdout',false); 156 | p.addParamValue('keepStderr',false); 157 | p.addParamValue('wrap',80); 158 | p.addParamValue('autoStart',true); 159 | p.addParamValue('verbose',false); 160 | p.addParamValue('pollInterval',0.05); 161 | p.parse(varargin{:}); 162 | 163 | if ~usejava('jvm') 164 | error([mfilename ' requires Java to run.']); 165 | end 166 | 167 | self.id = p.Results.id; 168 | self.workingDir = p.Results.workingDir; 169 | self.envp = p.Results.envp; 170 | self.printStdout = p.Results.printStdout; 171 | self.printStderr = p.Results.printStderr; 172 | self.keepStdout = p.Results.keepStdout; 173 | self.keepStderr = p.Results.keepStderr; 174 | self.wrap = p.Results.wrap; 175 | self.autoStart = p.Results.autoStart; 176 | self.verbose = p.Results.verbose; 177 | self.pollInterval = p.Results.pollInterval; 178 | 179 | self.state = processState(); 180 | 181 | self.command = p.Results.command; 182 | end 183 | 184 | function set.id(self,id) 185 | if ischar(id) 186 | self.id = id; 187 | elseif isscalar(id) 188 | self.id = num2str(id); 189 | else 190 | error('processManager:id:InputFormat','id must be scalar.'); 191 | end 192 | end 193 | 194 | function set.command(self,command) 195 | if iscell(command) 196 | % StringTokenizer is used to parse the command based on spaces 197 | % this may not be what we want, there is an overload of exec() 198 | % that allows passing in a String array. 199 | % http://www.mathworks.com/matlabcentral/newsreader/view_thread/308816 200 | n = length(command); 201 | cmdArray = javaArray('java.lang.String',n); 202 | for i = 1:n 203 | cmdArray(i) = java.lang.String(command{i}); 204 | end 205 | self.command = cmdArray; 206 | elseif ischar(command) 207 | self.command = command; 208 | elseif isa(command,'java.lang.String[]') || isa(command,'java.lang.String') 209 | self.command = command; 210 | else 211 | error('processManager:command:InputFormat',... 212 | 'command must be a string, cell array of strings, or java.lang.String array.'); 213 | end 214 | 215 | if self.autoStart && ~isempty(self.command) 216 | self.start(); 217 | end 218 | end 219 | 220 | function set.workingDir(self,workingDir) 221 | if ~ischar(workingDir) 222 | error('processManager:workingDir:InputFormat',... 223 | 'command must be a string specifying a directory.'); 224 | end 225 | if isempty(workingDir) 226 | self.workingDir = pwd; 227 | elseif exist(workingDir,'dir') == 7 228 | self.workingDir = workingDir; 229 | else 230 | error('processManager:workingDir:InputFormat',... 231 | 'Not a valid directory name.'); 232 | end 233 | end 234 | 235 | function set.envp(self,envp) 236 | if isempty(envp) 237 | self.envp = []; 238 | elseif ischar(envp) 239 | temp = javaArray('java.lang.String',1); 240 | temp(1) = java.lang.String(envp); 241 | self.envp = temp; 242 | elseif iscell(envp) 243 | n = length(envp); 244 | cmdArray = javaArray('java.lang.String',n); 245 | for i = 1:n 246 | cmdArray(i) = java.lang.String(envp{i}); 247 | end 248 | self.envp = cmdArray; 249 | else 250 | error('processManager:envp:InputFormat',... 251 | 'command must be a string, cell array of strings, or java.lang.String array.'); 252 | end 253 | end 254 | 255 | function set.printStdout(self,bool) 256 | if isscalar(bool) && islogical(bool) 257 | self.printStdout = bool; 258 | self.updatePollData('printStdout',bool); 259 | else 260 | error('processManager:printStdout:InputFormat',... 261 | 'Input must be a scalar logical'); 262 | end 263 | end 264 | 265 | function set.printStderr(self,bool) 266 | if isscalar(bool) && islogical(bool) 267 | self.printStderr = bool; 268 | self.updatePollData('printStderr',bool); 269 | else 270 | error('processManager:printStderr:InputFormat',... 271 | 'Input must be a scalar logical'); 272 | end 273 | end 274 | 275 | function set.keepStdout(self,bool) 276 | if isscalar(bool) && islogical(bool) 277 | self.keepStdout = bool; 278 | self.updatePollData('keepStdout',bool); 279 | else 280 | error('processManager:keepStdout:InputFormat',... 281 | 'Input must be a scalar logical'); 282 | end 283 | end 284 | 285 | function set.keepStderr(self,bool) 286 | if isscalar(bool) && islogical(bool) 287 | self.keepStderr = bool; 288 | self.updatePollData('keepStderr',bool); 289 | else 290 | error('processManager:keepStderr:InputFormat',... 291 | 'Input must be a scalar logical'); 292 | end 293 | end 294 | 295 | function stderr = get.stderr(self) 296 | if ~isempty(self.state) 297 | stderr = self.state.stderr; 298 | else 299 | stderr = {}; 300 | end 301 | end 302 | 303 | function stdout = get.stdout(self) 304 | if ~isempty(self.state) 305 | stdout = self.state.stdout; 306 | else 307 | stdout = {}; 308 | end 309 | end 310 | 311 | function set.wrap(self,x) 312 | if isscalar(x) && isnumeric(x) && (x>0) 313 | self.wrap = max(1,round(x)); 314 | self.updatePollData('wrap',self.wrap); 315 | else 316 | error('processManager:wrap:InputFormat',... 317 | 'Input must be a scalar > 0'); 318 | end 319 | end 320 | 321 | function set.autoStart(self,bool) 322 | if isscalar(bool) && islogical(bool) 323 | self.autoStart = bool; 324 | else 325 | error('processManager:autoStart:InputFormat',... 326 | 'Input must be a scalar logical'); 327 | end 328 | end 329 | 330 | function set.verbose(self,bool) 331 | if isscalar(bool) && islogical(bool) 332 | self.verbose = bool; 333 | self.updatePollData('verbose',bool); 334 | else 335 | error('processManager:verbose:InputFormat',... 336 | 'Input must be a scalar logical'); 337 | end 338 | end 339 | 340 | function set.pollInterval(self,x) 341 | if isscalar(x) && isnumeric(x) && (x>0) 342 | self.pollInterval = x; 343 | else 344 | error('processManager:pollInterval:InputFormat',... 345 | 'Input must be a scalar > 0'); 346 | end 347 | end 348 | 349 | function updatePollData(self,name,arg) 350 | for i = 1:numel(self) 351 | if ~isempty(self(i).pollTimer) && isvalid(self(i).pollTimer) 352 | dat = get(self.pollTimer,'UserData'); 353 | dat.(name) = arg; 354 | set(self.pollTimer,'UserData',dat); 355 | end 356 | end 357 | end 358 | 359 | function start(self) 360 | runtime = java.lang.Runtime.getRuntime(); 361 | for i = 1:numel(self) 362 | if isempty(self(i).command) 363 | continue; 364 | end 365 | try 366 | self(i).process = runtime.exec(self(i).command,... 367 | self(i).envp,... 368 | java.io.File(self(i).workingDir)); 369 | 370 | % Process will block if streams not drained 371 | self(i).stdoutReader = java.io.BufferedReader(... 372 | java.io.InputStreamReader(self(i).process.getInputStream())); 373 | self(i).stderrReader = java.io.BufferedReader(... 374 | java.io.InputStreamReader(self(i).process.getErrorStream())); 375 | 376 | % Install timer to periodically drain streams 377 | % http://stackoverflow.com/questions/8595748/java-runtime-exec 378 | self(i).pollTimer = timer(... 379 | 'ExecutionMode','FixedRate',... 380 | 'BusyMode','queue',... 381 | 'Period',self(i).pollInterval,... 382 | 'Name',[self(i).id '-processManager-pollTimer'],... 383 | 'StartFcn',@processManager.pollTimerStart,... 384 | 'StopFcn',@processManager.pollTimerStop,... 385 | 'TimerFcn',@processManager.poll); 386 | 387 | % Load data for the timer to avoid self reference 388 | pollData.verbose = self(i).verbose; 389 | pollData.printStderr = self(i).printStderr; 390 | pollData.printStdout = self(i).printStdout; 391 | pollData.wrap = self(i).wrap; 392 | pollData.stderr = self(i).stderr; 393 | pollData.stdout = self(i).stdout; 394 | pollData.keepStderr = self(i).keepStderr; 395 | pollData.keepStdout = self(i).keepStdout; 396 | pollData.stderrReader = self(i).stderrReader; 397 | pollData.stdoutReader = self(i).stdoutReader; 398 | pollData.process = self(i).process; 399 | pollData.state = self(i).state; 400 | pollData.state.id = self(i).id; 401 | set(self(i).pollTimer,'UserData',pollData); 402 | 403 | start(self(i).pollTimer); 404 | catch err 405 | if any(strfind(err.message,'java.io.IOException: error=2, No such file or directory')) 406 | error('processManager:start:InputFormat',... 407 | 'Looks like command doesn''t exist. Check spelling or path?'); 408 | else 409 | rethrow(err); 410 | end 411 | end 412 | end 413 | end 414 | 415 | function stop(self) 416 | for i = 1:numel(self) 417 | if ~isempty(self(i).process) 418 | self(i).process.destroy(); 419 | self(i).process.getErrorStream().close() 420 | self(i).process.getInputStream().close() 421 | self(i).process.getOutputStream().close(); 422 | self(i).stdoutReader.close(); 423 | self(i).stderrReader.close(); 424 | end 425 | end 426 | end 427 | 428 | function running = get.running(self) 429 | if isempty(self.process) 430 | running = false; 431 | else 432 | running = self.isRunning(self.process); 433 | end 434 | end 435 | 436 | function exitValue = get.exitValue(self) 437 | if isempty(self.process) 438 | exitValue = NaN; 439 | else 440 | [~,exitValue] = self.isRunning(self.process); 441 | end 442 | end 443 | 444 | function check(self) 445 | for i = 1:numel(self) 446 | if ~self(i).running && isa(self(i).process,'java.lang.Process') 447 | fprintf('Process %s finished with exit value %g.\n',self(i).id,self(i).exitValue); 448 | elseif self(i).running && isa(self(i).process,'java.lang.Process') 449 | fprintf('Process %s is still running.\n',self(i).id); 450 | else 451 | fprintf('Process %s has not been started yet.\n',self(i).id); 452 | end 453 | end 454 | end 455 | 456 | function block(self,t) 457 | % Avoid p.waitfor since it hangs with enough output, and 458 | % unfortunately, Matlab's waitfor does not work? 459 | % http://undocumentedmatlab.com/blog/waiting-for-asynchronous-events/ 460 | if nargin < 2 461 | t = self.pollInterval; 462 | end 463 | while any([self.running]) 464 | % Matlab pause() has a memory leak 465 | % http://undocumentedmatlab.com/blog/pause-for-the-better/ 466 | % http://matlabideas.wordpress.com/2013/05/18/take-a-break-have-a-pause/ 467 | java.lang.Thread.sleep(t*1000); 468 | end 469 | self.stop(); 470 | end 471 | 472 | function delete(self) 473 | if ~isempty(self.process) 474 | self.process.destroy(); 475 | self.process.getErrorStream().close() 476 | self.process.getInputStream().close() 477 | self.process.getOutputStream().close(); 478 | self.stdoutReader.close(); 479 | self.stderrReader.close(); 480 | end 481 | if ~isempty(self.pollTimer) 482 | if isvalid(self.pollTimer) 483 | stop(self.pollTimer); 484 | end 485 | end 486 | if self.verbose 487 | fprintf('processManager cleaned up.\n'); 488 | end 489 | end 490 | end 491 | 492 | methods(Static) 493 | function pollTimerStart(t,e) 494 | pollData = get(t,'UserData'); 495 | if pollData.verbose 496 | fprintf('processManager starting timer for process %s.\n',pollData.state.id) 497 | end 498 | end 499 | 500 | function pollTimerStop(t,e) 501 | pollData = get(t,'UserData'); 502 | if pollData.verbose 503 | fprintf('processManager stopped, uninstalling timer for process %s.\n',pollData.state.id) 504 | end 505 | % Update pollData 506 | [running,exitValue] = processManager.isRunning(pollData.process); 507 | pollData.state.running = running; 508 | pollData.state.exitValue = exitValue; 509 | set(t,'UserData',[]); 510 | delete(t); 511 | end 512 | 513 | function poll(t,e) 514 | pollData = get(t,'UserData'); 515 | try 516 | stderr = processManager.readStream(pollData.stderrReader); 517 | stdout = processManager.readStream(pollData.stdoutReader); 518 | catch err 519 | if any(strfind(err.message,'java.io.IOException: Stream closed')) 520 | % pass, this can happen when processManager object is 521 | % stopped or cleared, and should be harmless 522 | if pollData.verbose 523 | fprintf('processManager timer is polling a closed stream!\n'); 524 | end 525 | else 526 | rethrow(err); 527 | end 528 | end 529 | 530 | if pollData.printStderr && exist('stderr','var') 531 | stderr = processManager.printStream(stderr,pollData.state.id,pollData.wrap); 532 | end 533 | if pollData.printStdout && exist('stdout','var') 534 | stdout = processManager.printStream(stdout,pollData.state.id,pollData.wrap); 535 | end 536 | 537 | if pollData.keepStderr && exist('stderr','var') 538 | pollData.state.updateStderr(stderr); 539 | end 540 | if pollData.keepStdout && exist('stdout','var') 541 | pollData.state.updateStdout(stdout); 542 | end 543 | 544 | % Run timer StopFcn callback if process is done 545 | running = processManager.isRunning(pollData.process); 546 | if ~running 547 | stop(t); 548 | end 549 | end 550 | 551 | function lines = readStream(stream) 552 | % This is potentially fragile since ready() only checks whether 553 | % there is an element in the buffer, not a complete line. 554 | % Therefore, readLine() can block if the process doesn't terminate 555 | % all output with a carriage return... 556 | % 557 | % Alternatives inlcude: 558 | % 1) Implementing own low level read() and readLine() 559 | % 2) perhaps java.nio non-blocking methods 560 | % 3) Custom java class for spawning threads to manage streams 561 | lines = {}; 562 | while true 563 | if stream.ready() 564 | line = stream.readLine(); 565 | if isnumeric(line) && isempty(line) 566 | % java null is empty double in matlab 567 | % http://www.mathworks.com/help/matlab/matlab_external/passing-data-to-a-java-method.html 568 | break; 569 | end 570 | c = char(line); 571 | lines = cat(1,lines,c); 572 | else 573 | break; 574 | end 575 | end 576 | end 577 | 578 | function str = printStream(c,prefix,wrap) 579 | if nargin < 2 580 | prefix = ''; 581 | end 582 | if nargin < 3 583 | wrap = 80; 584 | end 585 | str = {}; 586 | for i = 1:length(c) 587 | if exist('linewrap','file') == 2 588 | if isempty(prefix) 589 | tempStr = linewrap(c{i},wrap); 590 | else 591 | tempStr = linewrap([prefix ': ' c{i}],wrap); 592 | end 593 | else 594 | if isempty(prefix) 595 | tempStr = c{i}; 596 | else 597 | tempStr = [prefix ': ' c{i}]; 598 | end 599 | end 600 | str = cat(1,str,tempStr); 601 | end 602 | fprintf('%s\n',str{:}); 603 | end 604 | 605 | function [bool,exitValue] = isRunning(process) 606 | try 607 | exitValue = process.exitValue(); 608 | bool = false; 609 | catch err 610 | if any(... 611 | [strfind(err.message,'java.lang.IllegalThreadStateException: process hasn''t exited')... 612 | strfind(err.message,'java.lang.IllegalThreadStateException: process has not exited')]... 613 | ) 614 | bool = true; 615 | exitValue = NaN; 616 | else 617 | rethrow(err); 618 | end 619 | end 620 | end 621 | end 622 | end 623 | -------------------------------------------------------------------------------- /processState.m: -------------------------------------------------------------------------------- 1 | % PROCESSSTATE - Utility class for processManager 2 | % 3 | % Useful for sending notifications based on exitValue of process. 4 | % 5 | classdef processState < handle 6 | properties 7 | id 8 | running = 0 9 | exitValue = NaN 10 | stderr = {} 11 | stdout = {} 12 | end 13 | events 14 | exit 15 | exit_success 16 | exit_failure 17 | end 18 | 19 | methods 20 | function self = processState(varargin) 21 | end 22 | 23 | function set.running(self,val) 24 | self.running = val; 25 | end 26 | 27 | function set.exitValue(self,val) 28 | self.exitValue = val; 29 | if val >= 0 30 | notify(self,'exit'); 31 | end 32 | if val == 0 33 | notify(self,'exit_success'); 34 | end 35 | if val > 0 36 | notify(self,'exit_failure'); 37 | end 38 | end 39 | 40 | function updateStderr(self,msg) 41 | self.stderr = cat(1,self.stderr,msg); 42 | end 43 | 44 | function updateStdout(self,msg) 45 | self.stdout = cat(1,self.stdout,msg); 46 | end 47 | end 48 | end --------------------------------------------------------------------------------