First actual commit
[progcomp2012.git] / manager / program.cpp
1 #include <sstream>
2
3 #include <stdarg.h>
4
5 #include <cassert>
6
7 #include "thread_util.h"
8 #include "program.h"
9
10
11 using namespace std;
12
13 /**
14  * Constructor
15  * @param executablePath - path to the program that will be run
16  *
17  * Creates two pipes - one for each direction between the parent process and the AI program
18  * Forks the process. 
19  *      The child process closes unused sides of the pipe, and then calls exec to replace itself with the AI program
20  *      The parent process closes unused sides of the pipe, and sets up member variables - associates streams with the pipe fd's for convenience.
21  */
22 Program::Program(const char * executablePath) : input(NULL), output(NULL), pid(0)
23 {
24         int readPipe[2]; int writePipe[2];
25         assert(pipe(readPipe) == 0);
26         assert(pipe(writePipe) == 0);
27
28         pid = fork();
29         if (pid == 0)
30         {
31                 close(readPipe[0]);  //close end that parent reads from
32                 close(writePipe[1]); //close end that parent writes to
33
34                 //TODO: Fix possible bug here if the process is already a daemon
35                 assert(writePipe[0] != 0 && readPipe[1] != 1);
36                 dup2(writePipe[0],0); close(writePipe[0]); //pipe end child reads from goes to STDIN
37                 dup2(readPipe[1], 1); close(readPipe[1]); //pipe end child writes to goes to STDOUT
38
39                 //TODO: Somehow force the exec'd process to be unbuffered
40                 setbuf(stdin, NULL); //WARNING: These lines don't appear to have any affect
41                 setbuf(stdout, NULL); //You should add them at the start of the wrapped program.
42                                         //If your wrapped program is not written in C/C++, you will probably have a problem
43                                 
44
45
46                 execl(executablePath, executablePath, (char*)(NULL)); ///Replace process with desired executable
47                 fprintf(stderr, "Program::Program - Could not run program \"%s\"!\n", executablePath);
48                 exit(EXIT_FAILURE); //We will probably have to terminate the whole program if this happens
49         }
50         else
51         {
52                 close(writePipe[0]); //close end that child writes to
53                 close(readPipe[1]); //close end that child reads from
54
55                 input = fdopen(readPipe[0],"r"); output = fdopen(writePipe[1],"w");
56                 setbuf(input, NULL);
57                 setbuf(output, NULL);
58         }
59         
60 }
61
62 /**
63  * Destructor
64  * Writes EOF to the wrapped program and then closes all streams
65  * Kills the wrapped program if it does not exit within 1 second.
66  */
67 Program::~Program()
68 {
69         if (kill(pid, 0) == 0) //Check if the process created is still running...
70         {
71                 fputc(EOF, output); //If it was, tell it to stop with EOF
72                 sleep(1); //Give it 1 second to respond...
73                 if (kill(pid, 0) == 0) //Check if its still running
74                 {
75                         kill(pid, 9); //Slay the infidel mercilessly!
76                 }
77         }
78         fclose(input);
79         fclose(output);
80         
81 }
82
83
84
85
86
87 /**
88  * Sends a message to the wrapped AI program
89  * WARNING: Always prints a new line after the message (so don't include a new line)
90  *      This is because everything is always line buffered.
91  */
92 bool Program::SendMessage(const char * print, ...)
93 {
94         if (kill(pid, 0) != 0) //Is the process running...
95                 return false; 
96
97         va_list ap;
98         va_start(ap, print);
99
100         vfprintf(output, print, ap);
101         fprintf(output, "\n");
102
103         
104
105         va_end(ap);
106
107         return true;
108 }
109
110
111 /**
112  * Retrieves a message from the wrapped AI program, waiting a maximum amount of time
113  * @param buffer - C++ string to store the resultant message in
114  * @param timeout - Maximum amount of time to wait before failure. If timeout <= 0, then GetMessage will wait indefinately.
115  * @returns true if the response was recieved within the specified time, false if it was not, or an EOF was recieved, or the process was not running.
116  */
117 bool Program::GetMessage(string & buffer, double timeout)
118 {
119         if (kill(pid, 0) != 0)
120                 return false;
121
122         assert(&buffer != NULL);
123         GetterThread getterThread(input, buffer);
124         assert(&(getterThread.buffer) != NULL);
125         TimerThread timerThread(timeout*1000000);
126
127         getterThread.Start();
128         if (timeout > 0)
129                 timerThread.Start();
130
131         
132         while (!getterThread.Finished())
133         {
134                 if (timeout > 0 && timerThread.Finished())
135                 {
136                         getterThread.Stop();
137                         timerThread.Stop();
138                         return false;
139                 }
140         }
141
142         getterThread.Stop();
143         timerThread.Stop();
144
145         
146
147         if (buffer.size() == 1 && buffer[0] == EOF)
148                 return false;
149         return true;
150
151
152 }
153
154 /**
155  * Returns true iff the process is running
156  * @returns what I just said, fool
157  */
158 bool Program::Running() const
159 {
160         return (kill(pid,0) == 0);
161 }
162
163

UCC git Repository :: git.ucc.asn.au