Writing to a Non-Full Pipe Blocks the Process in C

98 Views Asked by At

I'm encountering an issue while working on an assignment that involves implementing a program to manage inter-process communication.

Simplified context: There's a pipe, let's call it A, and 16 processes, numbered from 0 to 15.

In pipe A, there are already 2*512 = 1024 bytes of message. Process 0 needs to read these 1024 bytes of message and then exit, while processes from 1 to 15 (inclusive) are supposed to write 512 bytes of zeros to pipe A. For some reason, the last process writing to pipe A hangs on the write operation. The order in which processes perform their tasks is arbitrary.

In my system, the pipe capacity is 65536, confirmed using:

int fd[2];
pipe(fd);
void* message = malloc(512);
for (int i = 0; i < 128; i++) {
    write(fd[1], message, 512);
}
free(message);

int nbytes = 0;
if (ioctl(fd[1], FIONREAD, &nbytes) >= 0) {
    printf("There are %d bytes in the pipe\n", nbytes);
}

Output: There are 65536 bytes in the pipe.

Before each write to pipe A, I print how many bytes are already in it. I know theoretically each process could print that pipe size is zero and then write to the pipe, but the problem persists even when each subsequent printed pipe size is larger than the previous one.

Here's the code executed by each process from 1 to 15:

int pipe_size = 0;
if (ioctl(PIPE_A_DESCRIPTOR, FIONREAD, &pipe_size) >= 0) {
    printf("check_1, process %d, there are %d bytes in the pipe %d\n", get_process_num(), pipe_size);
}
write(PIPE_A_DESCRIPTOR, message, 512);
printf("check_2, process %d\n", get_process_num());
fflush(stdout);

Process 0 reads the data using

for (int i = 0; i < 2; i++) {
    read(PIPE_A_DESCRIPTOR, buffer, 512);
}

Part of the output:

check_1, process 1, there are 3584 bytes in the pipe
check_2, process 1
check_1, process 3, there are 4096 bytes in the pipe
check_2, process 3
check_1, process 7, there are 4608 bytes in the pipe
check_2, process 7
check_1, process 8, there are 5120 bytes in the pipe
check_2, process 8
check_1, process 14, there are 5632 bytes in the pipe
check_2, process 14
check_1, process 10, there are 6144 bytes in the pipe
check_2, process 10
check_1, process 15, there are 6656 bytes in the pipe
check_2, process 15
check_1, process 12, there are 7168 bytes in the pipe
check_1, process 13, there are 7168 bytes in the pipe
check_2, process 12
check_2, process 13
check_1, process 11, there are 8192 bytes in the pipe
0 read first 512 bytes
0 read second 512 bytes

As shown, last 'check_2' is missing, and the process got stuck on the write operation.

I write a maximum of 18 messages of length 512 to pipe A throughout the entire program, so even if none are read, the pipe shouldn't be full. Even if 8192 is the maximum pipe size for some reason, process 0 should read 1024 bytes from pipe A, and the sleeping process should wake up.

What could be the reason behind this issue? Is there any situation where the pipe blocks other than when it's full? I'm not providing the entire code. Firstly, because it's an assignment, and secondly, its length would exceed 300 lines.

Thanks a lot.

1

There are 1 best solutions below

0
Craig Estey On

I'm not providing the entire code. Firstly, because it's an assignment, and secondly, its length would exceed 300 lines.

That's unfortunate because 300 lines is well within the limits. And, much of what probably causes your program to go wrong is in the code that you didn't show.

In particular, the placement of fork relative to pipe. Also, various close calls.

I have to speculate a bit as to what your code is doing. And, I have to assume the worst.

We would really like an MRE. For C, we would like a single .c file [with (e.g.) #include <stdio.h>]. But, no (e.g.) #include "myprogram.h". For runtime errors, it should compile cleanly. We should be able to download, build, and run it on our local systems [if we so choose].


So, this can only be a partial solution ...

  1. You're not checking the return value of write. Pipes can return "short" reads or writes. (e.g.) You can write 128 bytes, but the write returns 64. Likewise for read.
  2. You're doing ioctl(fd[1], FIONREAD, &nbytes). I'm not totally sure, but I think that's invalid because fd[1] is for writing.
  3. I'd do this on fd[0] in the receiver.
  4. In the writer, you're not closing fd[0] before the loop.
  5. In the writer, you're not closing fd[1] after the loop.
  6. In the reader you're not closing fd[1] before the loop.
  7. When pipe ends are not closed properly, processes can fail to get EOF conditions and can hang.