servers/vfs/tll.c
/* This file contains the implementation of the three-level-lock. */

#include "fs.h"
#include "glo.h"
#include "tll.h"
#include "threads.h"
#include <assert.h>

static int tll_append(tll_t *tllp, tll_access_t locktype);

static int tll_append(tll_t *tllp, tll_access_t locktype)
{
  struct worker_thread *queue;

  assert(self != NULL);
  assert(tllp != NULL);
  assert(locktype != TLL_NONE);

  /* Read-only and write-only requests go to the write queue. Read-serialized
   * requests go to the serial queue. Then we wait for an event to signal it's
   * our turn to go. */
  queue = NULL;
  if (locktype == TLL_READ || locktype == TLL_WRITE) {
	if (tllp->t_write == NULL)
		tllp->t_write = self;
	else
		queue = tllp->t_write;
  } else {
	if (tllp->t_serial == NULL)
		tllp->t_serial = self;
	else
		queue = tllp->t_serial;
  }

  if (queue != NULL) {	/* Traverse to end of queue */
	while (queue->w_next != NULL) queue = queue->w_next;
	queue->w_next = self;
  }
  self->w_next = NULL; /* End of queue */

  /* Now wait for the event it's our turn */
  worker_wait();

  tllp->t_current = locktype;
  tllp->t_status &= ~TLL_PEND;
  tllp->t_owner = self;

  if (tllp->t_current == TLL_READ) {
	tllp->t_readonly++;
	tllp->t_owner = NULL;
  } else if (tllp->t_current == TLL_WRITE)
	assert(tllp->t_readonly == 0);

  /* Due to the way upgrading and downgrading works, read-only requests are
   * scheduled to run after a downgraded lock is released (because they are
   * queued on the write-only queue which has priority). This results from the
   * fact that the downgrade operation cannot know whether the next locktype on
   * the write-only queue is really write-only or actually read-only. However,
   * that means that read-serialized requests stay queued, while they could run
   * simultaneously with read-only requests. See if there are any and grant
   * the head request access */
  if (tllp->t_current == TLL_READ && tllp->t_serial != NULL) {
	tllp->t_owner = tllp->t_serial;
	tllp->t_serial = tllp->t_serial->w_next;
	tllp->t_owner->w_next = NULL;
	assert(!(tllp->t_status & TLL_PEND));
	tllp->t_status |= TLL_PEND;
	worker_signal(tllp->t_owner);
  }

  return(OK);
}

void tll_downgrade(tll_t *tllp)
{
/* Downgrade three-level-lock tll from write-only to read-serialized, or from
 * read-serialized to read-only. Caveat: as we can't know whether the next
 * lock type on the write queue is actually read-only or write-only, we can't
 * grant access to that type. It will be granted access once we unlock. Also,
 * because we apply write-bias, we can't grant access to read-serialized
 * either, unless nothing is queued on the write-only stack. */

  assert(self != NULL);
  assert(tllp != NULL);
  assert(tllp->t_owner == self);

  switch(tllp->t_current) {
    case TLL_WRITE: tllp->t_current = TLL_READSER; break;
    case TLL_READSER:
	/* If nothing is queued on write-only, but there is a pending lock
	 * requesting read-serialized, grant it and keep the lock type. */

	if (tllp->t_write == NULL && tllp->t_serial != NULL) {
		tllp->t_owner = tllp->t_serial;
		tllp->t_serial = tllp->t_serial->w_next; /* Remove head */
		tllp->t_owner->w_next = NULL;
		assert(!(tllp->t_status & TLL_PEND));
		tllp->t_status |= TLL_PEND;
		worker_signal(tllp->t_owner);
	} else {
		tllp->t_current = TLL_READ;
		tllp->t_owner = NULL;
	}
	tllp->t_readonly++; /* Either way, there's one more read-only lock */
	break;
    default: panic("VFS: Incorrect lock state");
  }

  if (tllp->t_current != TLL_WRITE && tllp->t_current != TLL_READSER)
	assert(tllp->t_owner == NULL);
}

void tll_init(tll_t *tllp)
{
/* Initialize three-level-lock tll */
  assert(tllp != NULL);

  tllp->t_current = TLL_NONE;
  tllp->t_readonly = 0;
  tllp->t_status = TLL_DFLT;
  tllp->t_write = NULL;
  tllp->t_serial = NULL;
  tllp->t_owner = NULL;
}

int tll_islocked(tll_t *tllp)
{
  return(tllp->t_current != TLL_NONE);
}

int tll_locked_by_me(tll_t *tllp)
{
  assert(self != NULL);
  return(tllp->t_owner == self && !(tllp->t_status & TLL_PEND));
}

int tll_lock(tll_t *tllp, tll_access_t locktype)
{
/* Try to lock three-level-lock tll with type locktype */

  assert(self != NULL);
  assert(tllp != NULL);
  assert(locktype != TLL_NONE);

  self->w_next = NULL;

  if (locktype != TLL_READ && locktype != TLL_READSER && locktype != TLL_WRITE)
	panic("Invalid lock type %d\n", locktype);

  /* If this locking has pending locks, we wait */
  if (tllp->t_status & TLL_PEND)
	return tll_append(tllp, locktype);

  /* If we already own this lock don't lock it again and return immediately */
  if (tllp->t_owner == self) {
	assert(tllp->t_status == TLL_DFLT);
	return(EBUSY);
  }

  /* If this lock is not accessed by anyone, locktype is granted off the bat */
  if (tllp->t_current == TLL_NONE) {
	tllp->t_current = locktype;
	if (tllp->t_current == TLL_READ)
		tllp->t_readonly = 1;
	else { /* Record owner if locktype is read-serialized or write-only */
		tllp->t_owner = self;
	}
	if (tllp->t_current == TLL_WRITE)
		assert(tllp->t_readonly == 0);
	return(OK);
  }

  /* If the current lock is write-only, we have to wait for that lock to be
   * released (regardless of the value of locktype). */
  if (tllp->t_current == TLL_WRITE)
	return tll_append(tllp, locktype);

  /* However, if it's not and we're requesting a write-only lock, we have to
   * wait until the last read access is released (additional read requests
   * after this write-only requests are to be queued) */
  if (locktype == TLL_WRITE)
	return tll_append(tllp, locktype);

  /* We have to queue read and read-serialized requests if we have a write-only
   * request queued ("write bias") or when a read-serialized lock is trying to
   * upgrade to write-only. The current lock for this tll is either read or
   * read-serialized. */
  if (tllp->t_write != NULL || (tllp->t_status & TLL_UPGR))
	return tll_append(tllp, locktype);

  /* If this lock is in read-serialized mode, we can allow read requests and
   * queue read-serialized requests */
  if (tllp->t_current == TLL_READSER) {
	if (locktype == TLL_READ) {
		tllp->t_readonly++;
		return(OK);
	} else
		return tll_append(tllp, locktype);
  }

  /* Finally, if the current lock is read-only, we can change it to
   * read-serialized if necessary without a problem. */
  tllp->t_current = locktype; /* Either read-only or read-serialized */
  if (tllp->t_current == TLL_READ) {	/* We now have an additional reader */
	tllp->t_readonly++;
	tllp->t_owner = NULL;
  } else {
	assert(tllp->t_current != TLL_WRITE);
	tllp->t_owner = self;		/* We now have a new owner */
	self->w_next = NULL;
  }

  return(OK);
}

int tll_haspendinglock(tll_t *tllp)
{
/* Is someone trying to obtain a lock? */
  assert(tllp != NULL);

  /* Someone is trying to obtain a lock if either the write/read-only queue or
   * the read-serialized queue is not empty. */
  return(tllp->t_write != NULL || tllp->t_serial != NULL);
}

int tll_unlock(tll_t *tllp)
{
/* Unlock a previously locked three-level-lock tll */
  int signal_owner = 0;

  assert(self != NULL);
  assert(tllp != NULL);

  if (tllp->t_owner == NULL || tllp->t_owner != self) {
	/* This unlock must have been done by a read-only lock */
	tllp->t_readonly--;
	assert(tllp->t_readonly >= 0);
	assert(tllp->t_current == TLL_READ || tllp->t_current == TLL_READSER);

	/* If a read-serialized lock is trying to upgrade and there are no more
	 * read-only locks, the lock can now be upgraded to write-only */
	if ((tllp->t_status & TLL_UPGR) && tllp->t_readonly == 0)
		signal_owner = 1;
  }

  if (tllp->t_owner == self && tllp->t_current == TLL_WRITE)
	assert(tllp->t_readonly == 0);

  if(tllp->t_owner == self || (tllp->t_owner == NULL && tllp->t_readonly == 0)){
	/* Let another read-serialized or write-only request obtain access.
	 * Write-only has priority, but only after the last read-only access
	 * has left. Read-serialized access will only be granted if there is
	 * no pending write-only access request. */
	struct worker_thread *new_owner;
	new_owner = NULL;
	tllp->t_owner = NULL;	/* Remove owner of lock */

	if (tllp->t_write != NULL) {
		if (tllp->t_readonly == 0) {
			new_owner = tllp->t_write;
			tllp->t_write = tllp->t_write->w_next;
		}
	} else if (tllp->t_serial != NULL) {
		new_owner = tllp->t_serial;
		tllp->t_serial = tllp->t_serial->w_next;
	}

	/* New owner is head of queue or NULL if no proc is available */
	if (new_owner != NULL) {
		tllp->t_owner = new_owner;
		tllp->t_owner->w_next = NULL;
		assert(tllp->t_owner != self);
		signal_owner = 1;
	}
  }

  /* If no one is using this lock, mark it as not in use */
  if (tllp->t_owner == NULL) {
	if (tllp->t_readonly == 0)
		tllp->t_current = TLL_NONE;
	else
		tllp->t_current = TLL_READ;
  }

  if (tllp->t_current == TLL_NONE || tllp->t_current == TLL_READ) {
	if (!signal_owner) {
		tllp->t_owner = NULL;
	}
  }

  /* If we have a new owner or the current owner managed to upgrade its lock,
   * tell it to start/continue running */
  if (signal_owner) {
	assert(!(tllp->t_status & TLL_PEND));
	tllp->t_status |= TLL_PEND;
	worker_signal(tllp->t_owner);
  }

  return(OK);
}

void tll_upgrade(tll_t *tllp)
{
/* Upgrade three-level-lock tll from read-serialized to write-only */

  assert(self != NULL);
  assert(tllp != NULL);
  assert(tllp->t_owner == self);
  assert(tllp->t_current != TLL_READ); /* i.e., read-serialized or write-only*/
  if (tllp->t_current == TLL_WRITE) return;	/* Nothing to do */
  if (tllp->t_readonly != 0) {		/* Wait for readers to leave */
	assert(!(tllp->t_status & TLL_UPGR));
	tllp->t_status |= TLL_UPGR;
	worker_wait();
	tllp->t_status &= ~TLL_UPGR;
	tllp->t_status &= ~TLL_PEND;
	assert(tllp->t_readonly == 0);
  }
  tllp->t_current = TLL_WRITE;
}
