
On Mon, Apr 04, 2011 at 01:31:40PM -0600, Eric Blake wrote:
On 04/04/2011 06:55 AM, Daniel P. Berrange wrote:
Allow the parent process to perform a bi-directional handshake with the child process during fork/exec. The child process will fork and do its initial setup. Immediately prior to the exec(), it will stop & wait for a handshake from the parent process. The parent process will spawn the child and wait until the child reaches the handshake point. It will do whatever extra setup work is required, before signalling the child to continue.
The implementation of this is done using two pairs of blocking pipes. The first pair is used to block the parent, until the child writes a single byte. Then the second pair pair is used to block the child, until the parent confirms with another single byte.
How worried are we about the child not doing any async-unsafe actions if it wishes to avoid deadlock? We've already previously identified this as a bug (such as in our handling of malloc in the child), but it's somewhat of a corner-case, and I'm not sure how invasive it will be to fix properly; so I am okay if a fixed version of this patch goes in while we still leave that larger issue open for thought.
IMHO, this patch doesn't make the existing problem any worse. eg, if VIR_DEBUG was going to cause deadlock, it would have happened before we get as far this new code I'm adding.
+++ b/src/util/command.c @@ -35,6 +35,11 @@ #include "files.h" #include "buf.h"
+#include <stdlib.h> +#include <stdbool.h>
"internal.h" guaranteed this one for us.
Opps, yes.
+#include <poll.h> +#include <sys/wait.h>
Why do we need this one? We're already using WIFEXITED and friends without explicitly needing this header.
I'll double check whether we need this.
@@ -1115,7 +1129,6 @@ virCommandRun(virCommandPtr cmd, int *exitstatus) return ret; }
- /* * Perform all virCommand-specific actions, along with the user hook. */
Spurious whitespace change?
@@ -1125,12 +1138,61 @@ virCommandHook(void *data) virCommandPtr cmd = data; int res = 0;
- if (cmd->hook) + if (cmd->hook) { + VIR_DEBUG("Run hook %p %p", cmd->hook, cmd->opaque); res = cmd->hook(cmd->opaque); + VIR_DEBUG("Done hook %d", res);
This adds yet more calls to malloc() inside the child
+ } if (res == 0 && cmd->pwd) { VIR_DEBUG("Running child in %s", cmd->pwd);
...but no worse than what we've already been doing. If VIR_DEBUG were the only use of malloc() in the child, then it could just boil down to conditionally compiling VIR_DEBUG statements where they can be turned on for debugging the implementation, but compiled out later to avoid deadlock (at least, I'm assuming that VIR_DEBUG uses malloc() to format a timestamp prior to the message when posting to the circular buffer).
In addition, VIR_DEBUG should be a no-op in normal operation. Only if someone raises the debug level significantly will it do work that triggers malloc & thus exposes the risk.
res = chdir(cmd->pwd); + if (res < 0) { + virReportSystemError(errno, + _("Unable to change to %s"), cmd->pwd); + } + } + if (cmd->handshake) { + char c = res < 0 ? '0' : '1'; + int rv; + VIR_DEBUG("Notifying parent for handshake start on %d", cmd->handshakeWait[1]); + if (safewrite(cmd->handshakeWait[1], &c, sizeof(c)) != sizeof(c)) {
Since c is char, sizeof(c) == 1 by definition. But I'm okay with you spelling out the longer form.
+ virReportSystemError(errno, "%s", _("Unable to notify parent process")); + return -1; + } + + /* On failure we pass the error message back to parent, + * so they don't have to dig through stderr logs + */ + if (res < 0) { + virErrorPtr err = virGetLastError(); + const char *msg = err ? err->message : + _("Unknown failure during hook execution");
Hmm, now that's a lot more use of malloc(), and not just in VIR_DEBUG() calls.
Code earlier that actually use virRaiseError would have already triggered malloc, so I still think this isn't making things any worse.
+ +void virCommandRequireHandshake(virCommandPtr cmd) +{ + if (pipe(cmd->handshakeWait) < 0) {
NULL dereference if cmd had a previous failure. You need to guard with:
if (!cmd || cmd->has_error) return;
Opps, yeah.
+ +int virCommandHandshakeWait(virCommandPtr cmd) +{ + char c; + int rv; + VIR_DEBUG("Wait for handshake on %d", cmd->handshakeWait[0]);
Likewise.
+ +int virCommandHandshakeNotify(virCommandPtr cmd) +{ + char c = '1'; + VIR_DEBUG("Notify handshake on %d", cmd->handshakeWait[0]);
Likewise.
+++ b/src/util/command.h @@ -274,6 +274,11 @@ int virCommandRunAsync(virCommandPtr cmd, int virCommandWait(virCommandPtr cmd, int *exitstatus) ATTRIBUTE_RETURN_CHECK;
+void virCommandRequireHandshake(virCommandPtr cmd); + +int virCommandHandshakeWait(virCommandPtr cmd); +int virCommandHandshakeNotify(virCommandPtr cmd);
These last two could use ATTRIBUTE_RETURN_CHECK. All three could use documentation.
Ok. Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|