u-jeivan%sunset.utah.edu@utah-gr.UUCP (Eric Ivancich) (06/22/88)
I have a question concerning deadlock. I fear that everyone knows this but me, but I have to learn somehow. I understand the basic concept of deadlock--two (in a simple case) entities, both waiting for an action of the other. However, I do not understand why the attached code produces deadlock. Provided Segment_B is commented out, everything works. Segment_A fires and then Segment_C does. But, if Segment_B is not commented out, I get deadlock; only Segment_A fires. This is not how I thought it would run. It seems that Segment_A should fire, followed by Segment_C, followed by Segment_D, and then Segment_B. What is wrong with my logic? Please respond by e-mail, and I will summarize to the net. Thanks, Eric ------------------------------------------------------------------------------- #include <stdio.h> #define STDIN 0 #define STDOUT 1 #define STDERR 2 #define READ 0 #define WRITE 1 main () { int p2c [2], c2p [2]; char buffer [128]; pipe (p2c); /* create pipes */ pipe (c2p); if (fork ()) { /* PARENT */ close (STDIN); /* parent reads from child */ dup (c2p [READ]); close (c2p [READ]); close (STDOUT); /* parent writes to child */ dup (p2c [WRITE]); close (p2c [WRITE]); /* SEGMENT_A */ fprintf (stderr, "Parent sends message\n"); printf ("Parent to child\n"); /* SEGMENT_B */ /* gets (buffer); * fprintf (stderr, "Parent receives: %s\n", buffer); */ } else { /* CHILD */ close (STDIN); /* child reads from parent */ dup (p2c [READ]); close (p2c [READ]); close (STDOUT); /* child writes to parent */ dup (c2p [WRITE]); close (c2p [WRITE]); /* SEGMENT_C */ gets (buffer); fprintf (stderr, "Child receives: %s\n", buffer); /* SEGMENT_D */ fprintf (stderr, "Child sends message\n"); printf ("Child to parent\n"); } } |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| They pelted us with rocks and garbage. - Late Night with David Letterman INFO: Eric Ivancich : University of Utah UUCP: {ihnp4, hplabs, decvax, arizona}!utah-ug!u-jeivan ARPA: u-jeivan@ug.utah.edu ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
u-jeivan%sunset.utah.edu@utah-gr.UUCP (Eric Ivancich) (06/23/88)
In article <2676@utah-gr.UUCP> u-jeivan@ug.utah.edu.UUCP (I) write: >I have a question concerning deadlock. I fear that everyone knows >this but me, but I have to learn somehow. I understand the basic >concept of deadlock--two (in a simple case) entities, both waiting for >an action of the other. However, I do not understand why the attached >code produces deadlock. >... As promised, I will summarize. First of all, thanks to Dieter, Greg Limes, Tim Olson, and John P. Nelson for responding. The problem isn't deadlock at all, but a buffering problem. John P. Nelson writes: | You are getting bitten by stdio buffering. This is fairly well-known, ! but is a common misunderstanding with novices. | ! The workaround is to put the line "fflush(stdout)" just before Segment_B. | Then everything functions the way you expected. Your logic is flawless, ! but there was a factor that you were not taking into account. | ! The first time a stdout is written to, the code checks to see what kind | of low-level descriptor is attached to it: If it is a terminal, then ! "stdout" is either unbuffered or line-buffered (depending on the version | of unix you are running). If the file descriptor is attached to a ! pipe or file, then the stdio descriptor is "fully buffered": I.e. no | output actually occurs until the buffer (BUFSIZ) is full, or until ! an explicit flush() is performed. | ! This behavior almost always does the "right thing": interactive tasks | get immediate response, and files and pipes get the most efficient ! buffering. The problem is because you are using a pipe in an interactive | manner (which is quite unusual!), the stdio defaults break down. ! | Besides calling "flush" explicitly each time you expect to "turn-around" ! to a read operation, you can change the buffering characteristics of | the stdout explicitly using "setbuf(stdout, NULL)", (or preferably ! "setlinebuf(stdout)", if your library supports it). Setbuf(...,NULL) | causes a "FILE *" to be completely unbuffered: Every stdio write operation ! will result in a low-level "write()" call. Setlinebuf is similar, except | that the buffer is only flushed on newlines, instead of on every operation. Now I know. Eric |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| They pelted us with rocks and garbage. - Late Night with David Letterman INFO: Eric Ivancich : University of Utah UUCP: {ihnp4, hplabs, decvax, arizona}!utah-ug!u-jeivan ARPA: u-jeivan@ug.utah.edu ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
rbj@cmr.icst.nbs.gov (Root Boy Jim) (06/24/88)
? From: Eric Ivancich <u-jeivan%sunset.utah.edu@utah-gr.uucp> Below is a fragment the deadlock program: ? main () ? { ? int p2c [2], ? c2p [2]; ? char buffer [128]; ? pipe (p2c); /* create pipes */ ? pipe (c2p); ? if (fork ()) { ? /* PARENT */ ? close (STDIN); /* parent reads from child */ ? dup (c2p [READ]); ? close (c2p [READ]); ADD: close (c2p [WRITE]); ? close (STDOUT); /* parent writes to child */ ? dup (p2c [WRITE]); ? close (p2c [WRITE]); ADD: close (p2c [READ]); ? /* SEGMENT_A */ ? fprintf (stderr, "Parent sends message\n"); ? printf ("Parent to child\n"); ALSO: fclose(stdout); ? /* SEGMENT_B */ ? /* gets (buffer); ? * fprintf (stderr, "Parent receives: %s\n", buffer); ? */ ? } else { ? /* CHILD */ ? close (STDIN); /* child reads from parent */ ? dup (p2c [READ]); ? close (p2c [READ]); ADD: close (p2c [WRITE]); ? close (STDOUT); /* child writes to parent */ ? dup (c2p [WRITE]); ? close (c2p [WRITE]); ADD: close (c2p [READ]); ? /* SEGMENT_C */ ? gets (buffer); ? fprintf (stderr, "Child receives: %s\n", buffer); ? /* SEGMENT_D */ ? fprintf (stderr, "Child sends message\n"); ? printf ("Child to parent\n"); ALSO: fclose(stdout); ? } ? } While this is not your problem, you have not closed all the unneeded pipes. I have indicated the extra close statements needed with `ADD:' in column one. Remember that the child will inherit *all* open file descriptors from the parent, and thus both ends of the pipes need to be closed in both the parent and the child. If you run the program with the `ADD:' and the `ALSO:' lines, it should (hopefully) run, as the close flushes buffers for you. I suppose I could also quibble with your coding convention; I dislike spaces between the array and its subscript, between the function name and its argument list, as well as spaces around the `->', `.', `++', and `--' operators, and unary `*', `!', `~', and `&', which you didn't use, but I have seen elsewhere. The rationale is that all these operators bind so tightly (in fact I consider `->', `.', and `[]' to be more like variable name specifiers than `operators') that they shoul be written that way. Of course, it's all a matter of taste, which you are free to define. ? ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ? They pelted us with rocks and garbage. - Late Night with David Letterman ? INFO: Eric Ivancich : University of Utah ? UUCP: {ihnp4, hplabs, decvax, arizona}!utah-ug!u-jeivan ? ARPA: u-jeivan@ug.utah.edu ? ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| (Root Boy) Jim Cottrell <rbj@icst-cmr.arpa> National Bureau of Standards Flamer's Hotline: (301) 975-5688 The opinions expressed are solely my own and do not reflect NBS policy or agreement Careful with that VAX Eugene!