[comp.unix.questions] Question on Deadlock

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!