CS360 Makeup Final Exam - May 4, 2012 - James S. Plank

Answers and Grading


Question 1

Grading

Three points per definition.


Question 2

Part A: Your program is generating SIGPIPE. At the time that the server drops your connection, you are usually in the sleep() call. When you wake up, you write "GET-QUOTES" to the server, but the connection is gone. Thus, the fflush() call generates SIGPIPE and the process dies.

Part B: To fix this, you should simply do what we advocated in class. Write a signal handler for SIGPIPE and have it do nothing but register itself as the handler for future SIGPIPE signals. Then, test fflush() for failure, and if it fails, re-establish the connection. This code is in a while() loop, because the first thing that you want to do when you re-open the connection is to write "GET-QUOTES\n" to the server.

I've put the new code in red.

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

typedef struct {
  char *host;
  int port;
  int qtime;
  FILE *fin;
  FILE *fout;
  int socket;
} Ripoff_Info;
  
void ignore_sigpipe(int x)
{
  signal(SIGPIPE, ignore_sigpipe);
  return;
}

void establish(Ripoff_Info *r, int close_em)
{
  if (close_em) {
    fclose(r->fin);
    fclose(r->fout);
    close(r->socket);
  }
  r->socket = request_connection(r->host, r->port);
  r->fin = fdopen(r->socket, "r");
  r->fout = fdopen(r->socket, "w");
}

void read_quotes(Ripoff_Info *r)
{
  signal(SIGPIPE, ignore_sigpipe);
  establish(r, 0);

  while (1) {
    sleep(r->qtime);
    while (fprintf(r->fout, "GET-QUOTES\n") < 0 || fflush(r->fout) != 0) establish(r, 1);
    if (read_and_display_quotes(r->fin) == 0) establish(r, 1);
  }
}

Grading

If you caught it using a global variable, you lost a point.


Question 3

Part A: Once you call execl(), the program's address space is overwritten. Threads A and B will both be gone at this point. You may think, "If the operating system has allocated threads for A and B, maybe they'll exist after the execl() call." However, they cannot exist, because their memory is gone -- no code, globals, heap or stack. The operating system will get rid of the threads -- operating systems are smart like that.

Part B: Nuts and bolts fork(), execl(), wait():

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

main()
{
  int pid, status;

  while (1) {
    if (fork() == 0) {
      execl("./ripoff", "ripoff", "servier.ripoff.com", "5555", "1", NULL);
      exit(1);
    } else {
      pid = wait(&status);
    }
  }
}

Does this program scare you a little? It does me -- if the execl() call fails, then we're going to be in an infinite fork()/execl()/wait() loop. It's not as bad as a fork bomb, though, since there are only two processes that exist at any one time, and you can kill the parent and stop the loop.

Grading


Question 4

Here's my answer. I ignore SIGPIPE and let fflush() catch problems when the write ends go away. I'll talk more about the exit semantics later. I call signal() in both threads because I don't know which thread will execute first.

Note the use of the condition variable. I have ripoff_to_server() hold the lock while it calls fputs() and fflush(). That might seem bad, but the only time that server_to_ripoff() tries to lock the mutex is when it has detected that the connection to the server has been lost and it is reconnecting. In that case, the fflush() call will fail and release the mutex, waiting for the connection to be reestablished.

void ignore_sigpipe(int x)
{
  signal(SIGPIPE, ignore_sigpipe);
  return;
}

void *ripoff_to_server(void *arg)
{
  Thread_Info *ti;
  char buffer[510];
  
  ti = (Thread_Info *) arg;

  signal(SIGPIPE, ignore_sigpipe);

  while (1) {
    if (fgets(buffer, 510, ti->fin_ripoff) == NULL) exit(0);
    pthread_mutex_lock(ti->lock);
    while (fputs(buffer, ti->fout_server) == EOF || fflush(ti->fout_server) != 0) {
      pthread_cond_wait(ti->cond, ti->lock);
    }
    pthread_mutex_unlock(ti->lock);
  }
}

void *server_to_ripoff(void *arg)
{
  Thread_Info *ti;
  char buffer[510];

  ti = (Thread_Info *) arg;

  signal(SIGPIPE, ignore_sigpipe);

  while (1) {
    if (fgets(buffer, 510, ti->fin_server) == NULL) {
      pthread_mutex_lock(ti->lock);
      fclose(ti->fin_server);
      fclose(ti->fout_server);
      close(ti->fd_server);
      ti->fd_server = request_connection("server.ripoff.com", 5555);
      ti->fin_server = fdopen(ti->fd_server, "r");
      ti->fout_server = fdopen(ti->fd_server, "w");
      pthread_cond_signal(ti->cond);
      pthread_mutex_unlock(ti->lock);
    } else {
      fputs(buffer, ti->fout_ripoff);
      if (fflush(ti->fout_ripoff) != 0) exit(0);
    }
  }
}

Part B: If the main thread exits naturally, the operating system terminates the process. pthread_exit() has the thread exit without terminating the process.

Part C:

  1. The read end of fd_ripoff goes away. This is detected by ripoff_to_server, by having fgets() return NULL. exit() is subsequently called, which terminates the program.

  2. The write end of fd_ripoff goes away. This is detected by having the signal handler ignore SIGPIPE, and having fflush() fail in server_to_ripoff(). When fflush() fails, exit() terminates the program.

  3. The read end of fd_server goes away. This is detected in server_to_ripoff() by having fgets() return NULL. When this happens, we close the old connections and establish new ones. We need to hold the lock during this process, because ripoff_to_server() may want to use ti->fout_server. When we get the new connection, we need to call pthread_cond_signal(), because ripoff_to_server() may have called pthread_cond_wait() when it couldn't write to the server.

  4. The write end of fd_server goes away. This is detected in ripoff_to_server() when fflush() or fputs() fails. When this happens, we call pthread_cond_wait() on the condition variable so that we can wait for server_to_ripoff() to discover that the connetion is gone, and reconnect.

You did not have to mimic my structure -- there are other valid ways to handle this problem. Even if you didn't handle everything well, if you explained what you did, that got you credit.

Grading