]> git.sur5r.net Git - bacula/bacula/blobdiff - bacula/src/stored/wait.c
Fix bug #2271 where poll interval causes tape mount message to repeat
[bacula/bacula] / bacula / src / stored / wait.c
index d95f3852e260a0f69a751a8d26185a138aa53d8b..655e7570d9f79b02dc06c3a3164c4dda36e83f1d 100644 (file)
@@ -1,3 +1,21 @@
+/*
+   Bacula(R) - The Network Backup Solution
+
+   Copyright (C) 2000-2017 Kern Sibbald
+
+   The original author of Bacula is Kern Sibbald, with contributions
+   from many others, a complete list can be found in the file AUTHORS.
+
+   You may use this file and others of this release according to the
+   license defined in the LICENSE file, which includes the Affero General
+   Public License, v3.0 ("AGPLv3") and some additional permissions and
+   terms pursuant to its AGPLv3 Section 7.
+
+   This notice must be preserved when any source code is 
+   conveyed and/or propagated.
+
+   Bacula(R) is a registered trademark of Kern Sibbald.
+*/
 /*
  *  Subroutines to handle waiting for operator intervention
  *   or waiting for a Device to be released
  *
  *   Kern Sibbald, March 2005
  *
- *   Version $Id$
  */
-/*
-   Copyright (C) 2000-2005 Kern Sibbald
 
-   This program is free software; you can redistribute it and/or
-   modify it under the terms of the GNU General Public License
-   version 2 as ammended with additional clauses defined in the
-   file LICENSE in the main source directory.
-
-   This program is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
-   the file LICENSE for additional details.
-
- */
 
 #include "bacula.h"                   /* pull in global headers */
 #include "stored.h"                   /* pull in Storage Deamon headers */
 
-static bool double_jcr_wait_time(JCR *jcr);
-
+const int dbglvl = 400;
 
 /*
  * Wait for SysOp to mount a tape on a specific device
  *
- *   Returns: status from pthread_cond_timedwait() 
+ *   Returns: W_ERROR, W_TIMEOUT, W_POLL, W_MOUNT, or W_WAKE
  */
 int wait_for_sysop(DCR *dcr)
 {
@@ -47,10 +50,16 @@ int wait_for_sysop(DCR *dcr)
    DEVICE *dev = dcr->dev;
    JCR *jcr = dcr->jcr;
 
-   P(dev->mutex);
-   unmounted = (dev->dev_blocked == BST_UNMOUNTED) ||
-                (dev->dev_blocked == BST_UNMOUNTED_WAITING_FOR_SYSOP);
+   dev->Lock();
+   Dmsg1(dbglvl, "Enter blocked=%s\n", dev->print_blocked());
 
+   /*
+    * Since we want to mount a tape, make sure current one is
+    *  not marked as using this drive.
+    */
+   volume_unused(dcr);
+
+   unmounted = dev->is_device_unmounted();
    dev->poll = false;
    /*
     * Wait requested time (dev->rem_wait_sec).  However, we also wake up every
@@ -70,25 +79,29 @@ int wait_for_sysop(DCR *dcr)
    }
 
    if (!unmounted) {
-      dev->dev_prev_blocked = dev->dev_blocked;
+      Dmsg1(dbglvl, "blocked=%s\n", dev->print_blocked());
+      dev->dev_prev_blocked = dev->blocked();
       dev->set_blocked(BST_WAITING_FOR_SYSOP); /* indicate waiting for mount */
    }
 
    for ( ; !job_canceled(jcr); ) {
-      time_t now, start;
+      time_t now, start, total_waited;
 
       gettimeofday(&tv, &tz);
       timeout.tv_nsec = tv.tv_usec * 1000;
       timeout.tv_sec = tv.tv_sec + add_wait;
 
-      Dmsg3(400, "I'm going to sleep on device %s. HB=%d wait=%d\n", dev->print_name(),
-         (int)me->heartbeat_interval, dev->wait_sec);
+      Dmsg4(dbglvl, "I'm going to sleep on device %s. HB=%d rem_wait=%d add_wait=%d\n",
+         dev->print_name(), (int)me->heartbeat_interval, dev->rem_wait_sec, add_wait);
       start = time(NULL);
+
       /* Wait required time */
-      stat = pthread_cond_timedwait(&dev->wait_next_vol, &dev->mutex, &timeout);
-      Dmsg1(400, "Wokeup from sleep on device stat=%d\n", stat);
+      stat = dev->next_vol_timedwait(&timeout);
 
+      Dmsg2(dbglvl, "Wokeup from sleep on device stat=%d blocked=%s\n", stat,
+         dev->print_blocked());
       now = time(NULL);
+      total_waited = now - first_start;
       dev->rem_wait_sec -= (now - start);
 
       /* Note, this always triggers the first time. We want that. */
@@ -96,156 +109,191 @@ int wait_for_sysop(DCR *dcr)
          if (now - last_heartbeat >= me->heartbeat_interval) {
             /* send heartbeats */
             if (jcr->file_bsock) {
-               bnet_sig(jcr->file_bsock, BNET_HEARTBEAT);
-               Dmsg0(400, "Send heartbeat to FD.\n");
+               jcr->file_bsock->signal(BNET_HEARTBEAT);
+               Dmsg0(dbglvl, "Send heartbeat to FD.\n");
             }
             if (jcr->dir_bsock) {
-               bnet_sig(jcr->dir_bsock, BNET_HEARTBEAT);
+               jcr->dir_bsock->signal(BNET_HEARTBEAT);
             }
             last_heartbeat = now;
          }
       }
 
+      if (stat == EINVAL) {
+         berrno be;
+         Jmsg1(jcr, M_FATAL, 0, _("pthread timedwait error. ERR=%s\n"), be.bstrerror(stat));
+         stat = W_ERROR;               /* error */
+         break;
+      }
+
       /*
-       * Check if user unmounted the device while we were waiting
+       * Continue waiting if operator is labeling volumes
        */
-      unmounted = (dev->dev_blocked == BST_UNMOUNTED) ||
-                   (dev->dev_blocked == BST_UNMOUNTED_WAITING_FOR_SYSOP);
-
-      if (stat != ETIMEDOUT) {     /* we blocked the device */
-         break;                    /* on error return */
+      if (dev->blocked() == BST_WRITING_LABEL) {
+         continue;
       }
+
       if (dev->rem_wait_sec <= 0) {  /* on exceeding wait time return */
-         Dmsg0(400, "Exceed wait time.\n");
+         Dmsg0(dbglvl, "Exceed wait time.\n");
+         stat = W_TIMEOUT;
          break;
       }
 
+      /*
+       * Check if user unmounted the device while we were waiting
+       */
+      unmounted = dev->is_device_unmounted();
+
       if (!unmounted && dev->vol_poll_interval &&
-          (now - first_start >= dev->vol_poll_interval)) {
-         Dmsg1(400, "In wait blocked=%s\n", edit_blocked_reason(dev));
+          (total_waited >= dev->vol_poll_interval)) {
+         Dmsg1(dbglvl, "Set poll=true return in wait blocked=%s\n", dev->print_blocked());
          dev->poll = true;            /* returning a poll event */
+         stat = W_POLL;
          break;
       }
       /*
        * Check if user mounted the device while we were waiting
        */
-      if (dev->dev_blocked == BST_MOUNT) {   /* mount request ? */
-         stat = 0;
+      if (dev->blocked() == BST_MOUNT) {   /* mount request ? */
+         Dmsg0(dbglvl, "Mounted return.\n");
+         stat = W_MOUNT;
          break;
       }
 
-      add_wait = dev->wait_sec - (now - start);
-      if (add_wait < 0) {
-         add_wait = 0;
+      /*
+       * If we did not timeout, then some event happened, so
+       *   return to check if state changed.
+       */
+      if (stat != ETIMEDOUT) {
+         berrno be;
+         Dmsg2(dbglvl, "Wake return. stat=%d. ERR=%s\n", stat, be.bstrerror(stat));
+         stat = W_WAKE;          /* someone woke us */
+         break;
       }
+
+      /*
+       * At this point, we know we woke up because of a timeout,
+       *   that was due to a heartbeat, because any other reason would
+       *   have caused us to return, so update the wait counters and continue.
+       */
+      add_wait = dev->rem_wait_sec;
       if (me->heartbeat_interval && add_wait > me->heartbeat_interval) {
          add_wait = me->heartbeat_interval;
       }
+      /* If the user did not unmount the tape and we are polling, ensure
+       *  that we poll at the correct interval.
+       */
+      if (!unmounted && dev->vol_poll_interval &&
+           add_wait > dev->vol_poll_interval - total_waited) {
+         add_wait = dev->vol_poll_interval - total_waited;
+      }
+      if (add_wait < 0) {
+         add_wait = 0;
+      }
    }
 
    if (!unmounted) {
       dev->set_blocked(dev->dev_prev_blocked);    /* restore entry state */
+      Dmsg1(dbglvl, "set %s\n", dev->print_blocked());
    }
-   V(dev->mutex);
+   Dmsg2(dbglvl, "Exit blocked=%s poll=%d\n", dev->print_blocked(), dev->poll);
+   dev->Unlock();
    return stat;
 }
 
 
 /*
- * Wait for any device to be released, then we return, so 
- *  higher level code can rescan possible devices.
- * 
+ * Wait for any device to be released, then we return, so
+ *  higher level code can rescan possible devices.  Since there
+ *  could be a job waiting for a drive to free up, we wait a maximum
+ *  of 1 minute then retry just in case a broadcast was lost, and
+ *  we return to rescan the devices.
+ *
  * Returns: true  if a device has changed state
  *          false if the total wait time has expired.
  */
-bool wait_for_device(JCR *jcr, bool first)
+bool wait_for_any_device(JCR *jcr, int &retries)
 {
    struct timeval tv;
    struct timezone tz;
    struct timespec timeout;
-// time_t last_heartbeat = 0;
    int stat = 0;
-   int add_wait;
-   bool ok = false;
+   bool ok = true;
+   const int max_wait_time = 1 * 60;       /* wait 1 minute */
+   char ed1[50];
 
-   Dmsg0(100, "Enter wait_for_device\n");
+   Dmsg0(dbglvl, "Enter wait_for_any_device\n");
    P(device_release_mutex);
 
-   if (first) {
-      Jmsg(jcr, M_MOUNT, 0, _("Job %s waiting to obtain a device.\n"), jcr->Job);
+   if (++retries % 5 == 0) {
+      /* Print message every 5 minutes */
+      Jmsg(jcr, M_MOUNT, 0, _("JobId=%s, Job %s waiting to reserve a device.\n"),
+         edit_uint64(jcr->JobId, ed1), jcr->Job);
    }
 
-   /*
-    * Wait requested time (dev->rem_wait_sec).  However, we also wake up every
-    *    HB_TIME seconds and send a heartbeat to the FD and the Director
-    *    to keep stateful firewalls from closing them down while waiting
-    *    for the operator.
-    */
-   add_wait = jcr->rem_wait_sec;
-   if (me->heartbeat_interval && add_wait > me->heartbeat_interval) {
-      add_wait = me->heartbeat_interval;
-   }
+   gettimeofday(&tv, &tz);
+   timeout.tv_nsec = tv.tv_usec * 1000;
+   timeout.tv_sec = tv.tv_sec + max_wait_time;
 
-   for ( ; !job_canceled(jcr); ) {
-      time_t now, start;
+   Dmsg0(dbglvl, "Going to wait for a device.\n");
 
-      gettimeofday(&tv, &tz);
-      timeout.tv_nsec = tv.tv_usec * 1000;
-      timeout.tv_sec = tv.tv_sec + add_wait;
+   /* Wait required time */
+   stat = pthread_cond_timedwait(&wait_device_release, &device_release_mutex, &timeout);
+   Dmsg1(dbglvl, "Wokeup from sleep on device stat=%d\n", stat);
 
-      Dmsg3(100, "I'm going to wait for a device. HB=%d wait=%d remwait=%d\n", 
-         (int)me->heartbeat_interval, jcr->wait_sec, jcr->rem_wait_sec);
-      start = time(NULL);
-      /* Wait required time */
-      stat = pthread_cond_timedwait(&wait_device_release, &device_release_mutex, &timeout);
-      Dmsg1(100, "Wokeup from sleep on device stat=%d\n", stat);
+   V(device_release_mutex);
+   Dmsg1(dbglvl, "Return from wait_device ok=%d\n", ok);
+   return ok;
+}
 
-      now = time(NULL);
-      jcr->rem_wait_sec -= (now - start);
+/*
+ * Wait for a specific device to be released
+ *  We wait a maximum of 1 minute then
+ *  retry just in case a broadcast was lost.
+ *
+ * Returns: true  if the device has changed state
+ *          false if the total wait time has expired.
+ */
+bool wait_for_device(DCR *dcr, int &retries)
+{
+   struct timeval tv;
+   struct timezone tz;
+   struct timespec timeout;
+   JCR *jcr = dcr->jcr;
+   DEVICE *dev = dcr->dev;
+   int stat = 0;
+   bool ok = true;
+   const int max_wait_time = 1 * 60;       /* wait 1 minute */
+   char ed1[50];
 
-#ifdef needed
-      /* Note, this always triggers the first time. We want that. */
-      if (me->heartbeat_interval) {
-         if (now - last_heartbeat >= me->heartbeat_interval) {
-            /* send heartbeats */
-            if (jcr->file_bsock) {
-               bnet_sig(jcr->file_bsock, BNET_HEARTBEAT);
-               Dmsg0(400, "Send heartbeat to FD.\n");
-            }
-            if (jcr->dir_bsock) {
-               bnet_sig(jcr->dir_bsock, BNET_HEARTBEAT);
-            }
-            last_heartbeat = now;
-         }
-      }
-#endif
+   Dmsg3(40, "Enter wait_for_device. busy=%d dcrvol=%s devvol=%s\n",
+      dev->is_busy(), dcr->VolumeName, dev->getVolCatName());
 
-      if (stat != ETIMEDOUT) {     /* if someone woke us up */
-         ok = true;
-         break;                    /* allow caller to examine device */
-      }
-      if (jcr->rem_wait_sec <= 0) {  /* on exceeding wait time return */
-         Dmsg0(400, "Exceed wait time.\n");
-         if (!double_jcr_wait_time(jcr)) {
-            break;                 /* give up */
-         }
-         Jmsg(jcr, M_MOUNT, 0, _("Job %s waiting to obtain a device.\n"), jcr->Job);
-      }
+   P(device_release_mutex);
 
-      add_wait = jcr->wait_sec - (now - start);
-      if (add_wait < 0) {
-         add_wait = 0;
-      }
-      if (me->heartbeat_interval && add_wait > me->heartbeat_interval) {
-         add_wait = me->heartbeat_interval;
-      }
+   if (++retries % 5 == 0) {
+      /* Print message every 5 minutes */
+      Jmsg(jcr, M_MOUNT, 0, _("JobId=%s, Job %s waiting device %s.\n"),
+         edit_uint64(jcr->JobId, ed1), jcr->Job, dcr->dev->print_name());
    }
 
+   gettimeofday(&tv, &tz);
+   timeout.tv_nsec = tv.tv_usec * 1000;
+   timeout.tv_sec = tv.tv_sec + max_wait_time;
+
+   Dmsg0(dbglvl, "Going to wait for a device.\n");
+
+   /* Wait required time */
+   stat = pthread_cond_timedwait(&wait_device_release, &device_release_mutex, &timeout);
+   Dmsg1(dbglvl, "Wokeup from sleep on device stat=%d\n", stat);
+
    V(device_release_mutex);
-   Dmsg1(100, "Return from wait_device ok=%d\n", ok);
+   Dmsg1(dbglvl, "Return from wait_device ok=%d\n", ok);
    return ok;
 }
 
+
+#ifdef xxx
 /*
  * The jcr timers are used for waiting on any device *
  * Returns: true if time doubled
@@ -264,3 +312,4 @@ static bool double_jcr_wait_time(JCR *jcr)
    }
    return true;
 }
+#endif