Skip to content
Snippets Groups Projects
Commit cf7c6f93 authored by Nat Goodspeed's avatar Nat Goodspeed
Browse files

Make pipe-management logic more robust.

Previous logic was vulnerable to the case in which both pipes reached EOF in
the same loop iteration. Now we use std::list instead of std::vector, allowing
us to iterate and delete with a single pass.
parent 6ccba881
No related branches found
No related tags found
No related merge requests found
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#include "llprocesslauncher.h" #include "llprocesslauncher.h"
// STL headers // STL headers
#include <vector> #include <vector>
#include <list>
// std headers // std headers
#include <errno.h> #include <errno.h>
// external library headers // external library headers
...@@ -28,7 +29,7 @@ ...@@ -28,7 +29,7 @@
#include "stringize.h" #include "stringize.h"
#if defined(LL_WINDOWS) #if defined(LL_WINDOWS)
#define sleep _sleep #define sleep(secs) _sleep((secs) * 1000)
#define EOL "\r\n" #define EOL "\r\n"
#else #else
#define EOL "\n" #define EOL "\n"
...@@ -244,44 +245,54 @@ namespace tut ...@@ -244,44 +245,54 @@ namespace tut
apr_proc_other_child_register(&child, child_status_callback, &wi, child.in, apr.pool); apr_proc_other_child_register(&child, child_status_callback, &wi, child.in, apr.pool);
// Monitor two different output pipes. Because one will be closed // Monitor two different output pipes. Because one will be closed
// before the other, keep them in a vector so we can drop whichever of // before the other, keep them in a list so we can drop whichever of
// them is closed first. // them is closed first.
typedef std::pair<std::string, apr_file_t*> DescFile; typedef std::pair<std::string, apr_file_t*> DescFile;
typedef std::vector<DescFile> DescFileVec; typedef std::list<DescFile> DescFileList;
DescFileVec outfiles; DescFileList outfiles;
outfiles.push_back(DescFile("out", child.out)); outfiles.push_back(DescFile("out", child.out));
outfiles.push_back(DescFile("err", child.err)); outfiles.push_back(DescFile("err", child.err));
while (! outfiles.empty()) while (! outfiles.empty())
{ {
DescFileVec iterfiles(outfiles); // This peculiar for loop is designed to let us erase(dfli). With
for (size_t i = 0; i < iterfiles.size(); ++i) // a list, that invalidates only dfli itself -- but even so, we
// lose the ability to increment it for the next item. So at the
// top of every loop, while dfli is still valid, increment
// dflnext. Then before the next iteration, set dfli to dflnext.
for (DescFileList::iterator
dfli(outfiles.begin()), dflnext(outfiles.begin()), dflend(outfiles.end());
dfli != dflend; dfli = dflnext)
{ {
// Only valid to increment dflnext once we're sure it's not
// already at dflend.
++dflnext;
char buf[4096]; char buf[4096];
apr_status_t rv = apr_file_gets(buf, sizeof(buf), iterfiles[i].second); apr_status_t rv = apr_file_gets(buf, sizeof(buf), dfli->second);
if (APR_STATUS_IS_EOF(rv)) if (APR_STATUS_IS_EOF(rv))
{ {
// std::cout << "(EOF on " << iterfiles[i].first << ")\n"; // std::cout << "(EOF on " << dfli->first << ")\n";
history.back().which = iterfiles[i].first; history.back().which = dfli->first;
history.back().what = "*eof*"; history.back().what = "*eof*";
history.push_back(Item()); history.push_back(Item());
outfiles.erase(outfiles.begin() + i); outfiles.erase(dfli);
continue; continue;
} }
if (rv == EWOULDBLOCK || rv == EAGAIN) if (rv == EWOULDBLOCK || rv == EAGAIN)
{ {
// std::cout << "(waiting; apr_file_gets(" << iterfiles[i].first << ") => " << rv << ": " << apr.strerror(rv) << ")\n"; // std::cout << "(waiting; apr_file_gets(" << dfli->first << ") => " << rv << ": " << apr.strerror(rv) << ")\n";
++history.back().tries; ++history.back().tries;
continue; continue;
} }
aprchk_("apr_file_gets(buf, sizeof(buf), iterfiles[i].second)", rv); aprchk_("apr_file_gets(buf, sizeof(buf), dfli->second)", rv);
// Is it even possible to get APR_SUCCESS but read 0 bytes? // Is it even possible to get APR_SUCCESS but read 0 bytes?
// Hope not, but defend against that anyway. // Hope not, but defend against that anyway.
if (buf[0]) if (buf[0])
{ {
// std::cout << iterfiles[i].first << ": " << buf; // std::cout << dfli->first << ": " << buf;
history.back().which = iterfiles[i].first; history.back().which = dfli->first;
history.back().what.append(buf); history.back().what.append(buf);
if (buf[strlen(buf) - 1] == '\n') if (buf[strlen(buf) - 1] == '\n')
history.push_back(Item()); history.push_back(Item());
...@@ -295,7 +306,7 @@ namespace tut ...@@ -295,7 +306,7 @@ namespace tut
} }
// Do this once per tick, as we expect the viewer will // Do this once per tick, as we expect the viewer will
apr_proc_other_child_refresh_all(APR_OC_REASON_RUNNING); apr_proc_other_child_refresh_all(APR_OC_REASON_RUNNING);
sleep(1); sleep(0.5);
} }
apr_file_close(child.in); apr_file_close(child.in);
apr_file_close(child.out); apr_file_close(child.out);
...@@ -313,7 +324,7 @@ namespace tut ...@@ -313,7 +324,7 @@ namespace tut
if (wi.rv == -1) if (wi.rv == -1)
{ {
std::cout << "child_status_callback() wasn't called\n"; std::cout << "child_status_callback(APR_OC_REASON_DEATH) wasn't called" << std::endl;
wi.rv = apr_proc_wait(wi.child, &wi.rc, &wi.why, APR_NOWAIT); wi.rv = apr_proc_wait(wi.child, &wi.rc, &wi.why, APR_NOWAIT);
} }
// std::cout << "child done: rv = " << rv << " (" << apr.strerror(rv) << "), why = " << why << ", rc = " << rc << '\n'; // std::cout << "child done: rv = " << rv << " (" << apr.strerror(rv) << "), why = " << why << ", rc = " << rc << '\n';
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment