rx: handle clock reversals for call timeouts
[openafs.git] / src / rx / rx.c
index 5857061..1eff5bb 100644 (file)
@@ -163,12 +163,12 @@ static unsigned int rxi_rpc_peer_stat_cnt;
 static unsigned int rxi_rpc_process_stat_cnt;
 
 /*
- * rxi_busyChannelError is the error to return to the application when a call
- * channel appears busy (inferred from the receipt of RX_PACKET_TYPE_BUSY
- * packets on the channel), and there are other call channels in the
- * connection that are not busy. If 0, we do not return errors upon receiving
- * busy packets; we just keep trying on the same call channel until we hit a
- * timeout.
+ * rxi_busyChannelError is a boolean.  It indicates whether or not RX_CALL_BUSY
+ * errors should be reported to the application when a call channel appears busy
+ * (inferred from the receipt of RX_PACKET_TYPE_BUSY packets on the channel),
+ * and there are other call channels in the connection that are not busy.
+ * If 0, we do not return errors upon receiving busy packets; we just keep
+ * trying on the same call channel until we hit a timeout.
  */
 static afs_int32 rxi_busyChannelError = 0;
 
@@ -755,17 +755,17 @@ rx_rto_setPeerTimeoutSecs(struct rx_peer *peer, int secs) {
 }
 
 /**
- * Sets the error generated when a busy call channel is detected.
+ * Enables or disables the busy call channel error (RX_CALL_BUSY).
  *
- * @param[in] error The error to return for a call on a busy channel.
+ * @param[in] onoff Non-zero to enable busy call channel errors.
  *
  * @pre Neither rx_Init nor rx_InitHost have been called yet
  */
 void
-rx_SetBusyChannelError(afs_int32 error)
+rx_SetBusyChannelError(afs_int32 onoff)
 {
     osi_Assert(rxinit_status != 0);
-    rxi_busyChannelError = error;
+    rxi_busyChannelError = onoff ? 1 : 0;
 }
 
 /**
@@ -1111,6 +1111,7 @@ void
 rx_SetConnIdleDeadTime(struct rx_connection *conn, int seconds)
 {
     conn->idleDeadTime = seconds;
+    conn->idleDeadDetection = (seconds ? 1 : 0);
     rxi_CheckConnTimeouts(conn);
 }
 
@@ -1550,6 +1551,7 @@ rx_NewCall(struct rx_connection *conn)
        }
        if (i < RX_MAXCALLS) {
            conn->lastBusy[i] = 0;
+           call->flags &= ~RX_CALL_PEER_BUSY;
            break;
        }
         if (!wait)
@@ -2006,7 +2008,7 @@ rx_GetCall(int tno, struct rx_service *cur_service, osi_socket * socketp)
                }
                MUTEX_ENTER(&rx_pthread_mutex);
                if (tno == rxi_fcfs_thread_num
-                   || !tcall->queue_item_header.next) {
+                       || queue_IsLast(&rx_incomingCallQueue, tcall)) {
                    MUTEX_EXIT(&rx_pthread_mutex);
                    /* If we're the fcfs thread , then  we'll just use
                     * this call. If we haven't been able to find an optimal
@@ -2980,8 +2982,8 @@ rxi_FindConnection(osi_socket socket, afs_uint32 host,
        conn->nSpecific = 0;
        conn->specific = NULL;
        rx_SetConnDeadTime(conn, service->connDeadTime);
-       rx_SetConnIdleDeadTime(conn, service->idleDeadTime);
-       rx_SetServerConnIdleDeadErr(conn, service->idleDeadErr);
+       conn->idleDeadTime = service->idleDeadTime;
+       conn->idleDeadDetection = service->idleDeadErr ? 1 : 0;
        for (i = 0; i < RX_MAXCALLS; i++) {
            conn->twind[i] = rx_initSendWindow;
            conn->rwind[i] = rx_initReceiveWindow;
@@ -3081,7 +3083,7 @@ rxi_CheckBusy(struct rx_call *call)
         * rxi_busyChannelError so the application can retry the request,
         * presumably on a less-busy call channel. */
 
-       rxi_CallError(call, rxi_busyChannelError);
+       rxi_CallError(call, RX_CALL_BUSY);
     }
 }
 
@@ -5008,18 +5010,27 @@ struct rx_packet *
 rxi_SendCallAbort(struct rx_call *call, struct rx_packet *packet,
                  int istack, int force)
 {
-    afs_int32 error;
+    afs_int32 error, cerror;
     struct clock when, now;
 
     if (!call->error)
        return packet;
 
+    switch (call->error) {
+    case RX_CALL_IDLE:
+    case RX_CALL_BUSY:
+        cerror = RX_CALL_TIMEOUT;
+        break;
+    default:
+        cerror = call->error;
+    }
+
     /* Clients should never delay abort messages */
     if (rx_IsClientConn(call->conn))
        force = 1;
 
-    if (call->abortCode != call->error) {
-       call->abortCode = call->error;
+    if (call->abortCode != cerror) {
+       call->abortCode = cerror;
        call->abortCount = 0;
     }
 
@@ -5029,7 +5040,7 @@ rxi_SendCallAbort(struct rx_call *call, struct rx_packet *packet,
            rxevent_Cancel(&call->delayedAbortEvent, call,
                           RX_CALL_REFCOUNT_ABORT);
        }
-       error = htonl(call->error);
+       error = htonl(cerror);
        call->abortCount++;
        packet =
            rxi_SendSpecial(call, call->conn, packet, RX_PACKET_TYPE_ABORT,
@@ -5245,9 +5256,13 @@ rxi_ResetCall(struct rx_call *call, int newcall)
     }
     call->flags = 0;
 
-    if ((flags & RX_CALL_PEER_BUSY)) {
+    if (!newcall && (flags & RX_CALL_PEER_BUSY)) {
        /* The call channel is still busy; resetting the call doesn't change
-        * that */
+        * that. However, if 'newcall' is set, we are processing a call
+        * structure that has either been recycled from the free list, or has
+        * been newly allocated. So, RX_CALL_PEER_BUSY is not relevant if
+        * 'newcall' is set, since it describes a completely different call
+        * channel which we do not care about. */
        call->flags |= RX_CALL_PEER_BUSY;
     }
 
@@ -6127,6 +6142,32 @@ rxi_CheckCall(struct rx_call *call)
     afs_uint32 fudgeFactor;
     int cerror = 0;
     int newmtu = 0;
+    int idle_timeout = 0;
+    afs_int32  clock_diff = 0;
+
+    now = clock_Sec();
+
+    /* Large swings in the clock can have a significant impact on
+     * the performance of RX call processing.  Forward clock shifts
+     * will result in premature event triggering or timeouts.
+     * Backward shifts can result in calls not completing until
+     * the clock catches up with the original start clock value.
+     *
+     * If a backward clock shift of more than five minutes is noticed,
+     * just fail the call.
+     */
+    if (now < call->lastSendTime)
+        clock_diff = call->lastSendTime - now;
+    if (now < call->startWait)
+        clock_diff = MAX(clock_diff, call->startWait - now);
+    if (now < call->lastReceiveTime)
+        clock_diff = MAX(clock_diff, call->lastReceiveTime - now);
+    if (clock_diff > 5 * 60)
+    {
+       if (call->state == RX_STATE_ACTIVE)
+           rxi_CallError(call, RX_CALL_TIMEOUT);
+       return -1;
+    }
 
 #ifdef AFS_GLOBAL_RXLOCK_KERNEL
     if (call->flags & RX_CALL_TQ_BUSY) {
@@ -6141,7 +6182,6 @@ rxi_CheckCall(struct rx_call *call)
                    ((afs_uint32) call->rtt_dev << 1) + 1023) >> 10;
 
     deadTime = conn->secondsUntilDead + fudgeFactor;
-    now = clock_Sec();
     /* These are computed to the second (+- 1 second).  But that's
      * good enough for these values, which should be a significant
      * number of seconds. */
@@ -6204,25 +6244,29 @@ rxi_CheckCall(struct rx_call *call)
         * attached process can die reasonably gracefully. */
     }
 
-    if (conn->idleDeadTime) {
-       idleDeadTime = conn->idleDeadTime + fudgeFactor;
-    }
+    if (conn->idleDeadDetection) {
+        if (conn->idleDeadTime) {
+            idleDeadTime = conn->idleDeadTime + fudgeFactor;
+        }
 
-    /* see if we have a non-activity timeout */
-    if (call->startWait && idleDeadTime
-       && ((call->startWait + idleDeadTime) < now) &&
-       (call->flags & RX_CALL_READER_WAIT)) {
-       if (call->state == RX_STATE_ACTIVE) {
-           cerror = RX_CALL_TIMEOUT;
-           goto mtuout;
-       }
-    }
-    if (call->lastSendData && idleDeadTime && (conn->idleDeadErr != 0)
-        && ((call->lastSendData + idleDeadTime) < now)) {
-       if (call->state == RX_STATE_ACTIVE) {
-           cerror = conn->idleDeadErr;
-           goto mtuout;
-       }
+        if (idleDeadTime) {
+            /* see if we have a non-activity timeout */
+            if (call->startWait && ((call->startWait + idleDeadTime) < now) &&
+                (call->flags & RX_CALL_READER_WAIT)) {
+                if (call->state == RX_STATE_ACTIVE) {
+                    cerror = RX_CALL_TIMEOUT;
+                    goto mtuout;
+                }
+            }
+
+            if (call->lastSendData && ((call->lastSendData + idleDeadTime) < now)) {
+                if (call->state == RX_STATE_ACTIVE) {
+                    cerror = conn->service ? conn->service->idleDeadErr : RX_CALL_IDLE;
+                    idle_timeout = 1;
+                    goto mtuout;
+                }
+            }
+        }
     }
 
     if (conn->hardDeadTime) {
@@ -6238,8 +6282,8 @@ rxi_CheckCall(struct rx_call *call)
     }
     return 0;
 mtuout:
-    if (conn->msgsizeRetryErr && cerror != RX_CALL_TIMEOUT
-       && call->lastReceiveTime) {
+    if (conn->msgsizeRetryErr && cerror != RX_CALL_TIMEOUT && !idle_timeout &&
+        call->lastReceiveTime) {
        int oldMTU = conn->peer->ifMTU;
 
        /* if we thought we could send more, perhaps things got worse */
@@ -6459,7 +6503,7 @@ rxi_GrowMTUEvent(struct rxevent *event, void *arg1, void *dummy, int dummy2)
      */
     if ((conn->peer->maxPacketSize != 0) &&
        (conn->peer->natMTU < RX_MAX_PACKET_SIZE) &&
-       (conn->idleDeadErr))
+       conn->idleDeadDetection)
        (void)rxi_SendAck(call, NULL, 0, RX_ACK_MTU, 0);
     rxi_ScheduleGrowMTUEvent(call, 0);
     MUTEX_EXIT(&call->lock);