/*
- Bacula® - The Network Backup Solution
-
- Copyright (C) 2000-2008 Free Software Foundation Europe e.V.
-
- The main author of Bacula is Kern Sibbald, with contributions from
- many others, a complete list can be found in the file AUTHORS.
- This program is Free Software; you can redistribute it and/or
- modify it under the terms of version two of the GNU General Public
- License as published by the Free Software Foundation and included
- in the file LICENSE.
-
- 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 GNU
- General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
- 02110-1301, USA.
-
- Bacula® is a registered trademark of John Walker.
- The licensor of Bacula is the Free Software Foundation Europe
- (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
- Switzerland, email:ftf@fsfeurope.org.
+ 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.
*/
/*
* Collection of Bacula Storage daemon locking software
*
- * Kern Sibbald, 2000-2007. June 2007
+ * Kern Sibbald, June 2007
*
- * Version $Id$
*/
#include "bacula.h" /* pull in global headers */
#include "stored.h" /* pull in Storage Deamon headers */
#ifdef SD_DEBUG_LOCK
-const int dbglvl = 0;
+const int dbglvl = 300;
#else
const int dbglvl = 500;
#endif
* is "marked" in use. When setting and removing the
block, the device is locked, but after dblock is
called the device is unlocked.
- * 2. dlock() simple mutex that locks the device structure. A dlock
+ * 2. Lock() simple mutex that locks the device structure. A Lock
* can be acquired while a device is blocked if it is not
- * locked.
- * 3. r_dlock "recursive" dlock, when means that a dlock (mutex)
+ * locked.
+ * 3. rLock(locked) "recursive" Lock, when means that a Lock (mutex)
* will be acquired on the device if it is not blocked
* by some other thread. If the device was blocked by
* the current thread, it will acquire the lock.
* If some other thread has set a block on the device,
* this call will wait until the device is unblocked.
+ * Can be called with locked true, which means the
+ * Lock is already set
*
* A lock is normally set when modifying the device structure.
- * A r_lock is normally acquired when you want to block the device
+ * A rLock is normally acquired when you want to block the device
* i.e. it will wait until the device is not blocked.
* A block is normally set during long operations like writing to
* the device.
- * If you are writing the device, you will normally block and
+ * If you are writing the device, you will normally block and
* lock it.
* A lock cannot be violated. No other thread can touch the
- * device while a lock is set.
+ * device while a lock is set.
* When a block is set, every thread accept the thread that set
- * the block will block if r_dlock is called.
+ * the block will block if rLock is called.
* A device can be blocked for multiple reasons, labeling, writing,
* acquiring (opening) the device, waiting for the operator, unmounted,
* ...
- * Under certain conditions the block that is set on a device can be
+ * Under certain conditions the block that is set on a device can be
* stolen and the device can be used by another thread. For example,
- * a device is blocked because it is waiting for the operator to
+ * a device is blocked because it is waiting for the operator to
* mount a tape. The operator can then unmount the device, and label
* a tape, re-mount it, give back the block, and the job will continue.
*
*
* Functions:
*
- * DEVICE::dlock() does P(m_mutex) (in dev.h)
- * DEVICE::dunlock() does V(m_mutex)
+ * DEVICE::Lock() does P(m_mutex) (in dev.h)
+ * DEVICE::Unlock() does V(m_mutex)
*
- * DEVICE::r_dlock() allows locking the device when this thread
- already has the device blocked.
- * dlock()
+ * DEVICE::rLock(locked) allows locking the device when this thread
+ * already has the device blocked.
+ * if (!locked)
+ * Lock()
* if blocked and not same thread that locked
* pthread_cond_wait
- * leaves device locked
+ * leaves device locked
*
- * DEVICE::r_dunlock() unlocks but does not unblock
- * same as dunlock();
+ * DEVICE::rUnlock() unlocks but does not unblock
+ * same as Unlock();
*
- * DEVICE::dblock(why) does
- * r_dlock(); (recursive device lock)
- * block_device(this, why)
- * r_dunlock()
+ * DEVICE::dblock(why) does
+ * rLock(); (recursive device lock)
+ * block_device(this, why)
+ * rUnlock()
*
* DEVICE::dunblock does
- * dlock()
+ * Lock()
* unblock_device()
- * dunlock()
+ * Unlock()
*
- * block_device() does (must be locked and not blocked at entry)
+ * block_device() does (must be locked and not blocked at entry)
* set blocked status
* set our pid
*
* save status
* set new blocked status
* set new pid
- * unlock()
+ * Unlock()
*
* give_back_device_lock() does (must be blocked but not locked)
- * dlock()
+ * Lock()
* reset blocked status
* save previous blocked
* reset pid
*
*/
-
void DEVICE::dblock(int why)
{
- r_dlock(); /* need recursive lock to block */
+ rLock(false); /* need recursive lock to block */
block_device(this, why);
- r_dunlock();
+ rUnlock();
}
void DEVICE::dunblock(bool locked)
{
if (!locked) {
- dlock();
+ Lock();
}
unblock_device(this);
- dunlock();
+ Unlock();
}
-#ifdef SD_DEBUG_LOCK
-void DEVICE::_dlock(const char *file, int line)
-{
- Dmsg4(sd_dbglvl, "dlock from %s:%d precnt=%d JobId=%u\n", file, line,
- m_count, get_jobid_from_tid());
- /* Note, this *really* should be protected by a mutex, but
- * since it is only debug code we don't worry too much.
- */
- if (m_count > 0 && pthread_equal(m_pid, pthread_self())) {
- Dmsg5(sd_dbglvl, "Possible DEADLOCK!! lock held by JobId=%u from %s:%d m_count=%d JobId=%u\n",
- get_jobid_from_tid(m_pid),
- file, line, m_count, get_jobid_from_tid());
- }
- P(m_mutex);
+
+/*
+ * Debug DEVICE locks N.B.
+ *
+ */
+
+#ifdef DEV_DEBUG_LOCK
+
+void DEVICE::dbg_Lock(const char *file, int line)
+{
+ Dmsg4(sd_dbglvl, "Lock %s from %s:%d precnt=%d\n", device->hdr.name, file, line, m_count);
+ bthread_mutex_lock_p(&m_mutex, file, line);
m_pid = pthread_self();
- m_count++;
+ m_count++;
}
-void DEVICE::_dunlock(const char *file, int line)
+void DEVICE::dbg_Unlock(const char *file, int line)
{
- m_count--;
- Dmsg4(sd_dbglvl+1, "dunlock from %s:%d postcnt=%d JobId=%u\n", file, line,
- m_count, get_jobid_from_tid());
- V(m_mutex);
+ m_count--;
+ clear_thread_id(m_pid);
+ Dmsg4(sd_dbglvl, "Unlock %s from %s:%d postcnt=%d\n", device->hdr.name, file, line, m_count);
+ bthread_mutex_unlock_p(&m_mutex, file, line);
}
-void DEVICE::_r_dunlock(const char *file, int line)
+void DEVICE::dbg_rUnlock(const char *file, int line)
{
- this->_dunlock(file, line);
+ Dmsg2(sd_dbglvl, "rUnlock from %s:%d\n", file, line);
+ dbg_Unlock(file, line);
}
-#endif
+#else
+
+/*
+ * DEVICE locks N.B.
+ *
+ */
+
+
+void DEVICE::rUnlock()
+{
+ Unlock();
+}
+
+void DEVICE::Lock()
+{
+ P(m_mutex);
+}
+
+void DEVICE::Unlock()
+{
+ V(m_mutex);
+}
+#endif /* DEV_DEBUG_LOCK */
/*
* This is a recursive lock that checks if the device is blocked.
* must wait. The no_wait_id thread is out obtaining a new volume
* and preparing the label.
*/
-#ifdef SD_DEBUG_LOCK
-void DEVICE::_r_dlock(const char *file, int line)
+#ifdef DEV_DEBUG_LOCK
+void DEVICE::dbg_rLock(const char *file, int line, bool locked)
{
- Dmsg4(sd_dbglvl+1, "r_dlock blked=%s from %s:%d JobId=%u\n", this->print_blocked(),
- file, line, get_jobid_from_tid());
-#else
-void DEVICE::r_dlock()
+ Dmsg3(sd_dbglvl, "Enter rLock blked=%s from %s:%d\n", print_blocked(),
+ file, line);
+ if (!locked) {
+ /* lockmgr version of P(m_mutex) */
+ Dmsg4(sd_dbglvl, "Lock %s in rLock %s from %s:%d\n",
+ device->hdr.name, print_blocked(), file, line);
+ bthread_mutex_lock_p(&m_mutex, file, line);
+ m_count++;
+ }
+
+ if (blocked() && !pthread_equal(no_wait_id, pthread_self())) {
+ num_waiting++; /* indicate that I am waiting */
+ while (blocked()) {
+ int stat;
+#ifndef HAVE_WIN32
+ /* thread id on Win32 may be a struct */
+ Dmsg5(sd_dbglvl, "Blocked by %d %s in rLock blked=%s no_wait=%p me=%p\n",
+ blocked_by, device->hdr.name, print_blocked(), no_wait_id, pthread_self());
+#endif
+ if ((stat = bthread_cond_wait_p(&this->wait, &m_mutex, file, line)) != 0) {
+ berrno be;
+ this->dbg_Unlock(file, line);
+ Emsg1(M_ABORT, 0, _("pthread_cond_wait failure. ERR=%s\n"),
+ be.bstrerror(stat));
+ }
+ }
+ num_waiting--; /* no longer waiting */
+ }
+}
+#else /* DEV_DEBUG_LOCK */
+
+void DEVICE::rLock(bool locked)
{
+ if (!locked) {
+ Lock();
+ m_count++;
+ }
+
+ if (blocked() && !pthread_equal(no_wait_id, pthread_self())) {
+ num_waiting++; /* indicate that I am waiting */
+ while (blocked()) {
+ int stat;
+#ifndef HAVE_WIN32
+ /* thread id on Win32 may be a struct */
+ Dmsg5(sd_dbglvl, "Blocked by %d rLock %s blked=%s no_wait=%p me=%p\n",
+ blocked_by, device->hdr.name, print_blocked(), no_wait_id, pthread_self());
#endif
- int stat;
- this->dlock();
- if (this->blocked() && !pthread_equal(this->no_wait_id, pthread_self())) {
- this->num_waiting++; /* indicate that I am waiting */
- while (this->blocked()) {
- Dmsg3(sd_dbglvl, "r_dlock blked=%s no_wait=%p me=%p\n", this->print_blocked(),
- this->no_wait_id, pthread_self());
if ((stat = pthread_cond_wait(&this->wait, &m_mutex)) != 0) {
berrno be;
- this->dunlock();
+ this->Unlock();
Emsg1(M_ABORT, 0, _("pthread_cond_wait failure. ERR=%s\n"),
be.bstrerror(stat));
}
}
- this->num_waiting--; /* no longer waiting */
+ num_waiting--; /* no longer waiting */
}
}
+#endif /* DEV_DEBUG_LOCK */
+
+#ifdef SD_DEBUG_LOCK
+
+void DEVICE::dbg_Lock_acquire(const char *file, int line)
+{
+ Dmsg2(sd_dbglvl, "Lock_acquire from %s:%d\n", file, line);
+ bthread_mutex_lock_p(&acquire_mutex, file, line);
+}
+
+void DEVICE::dbg_Unlock_acquire(const char *file, int line)
+{
+ Dmsg2(sd_dbglvl, "Unlock_acquire from %s:%d\n", file, line);
+ bthread_mutex_unlock_p(&acquire_mutex, file, line);
+}
+
+void DEVICE::dbg_Lock_read_acquire(const char *file, int line)
+{
+ Dmsg2(sd_dbglvl, "Lock_read_acquire from %s:%d\n", file, line);
+ bthread_mutex_lock_p(&read_acquire_mutex, file, line);
+}
+
+void DEVICE::dbg_Unlock_read_acquire(const char *file, int line)
+{
+ Dmsg2(sd_dbglvl, "Unlock_read_acquire from %s:%d\n", file, line);
+ bthread_mutex_unlock_p(&read_acquire_mutex, file, line);
+}
+
+void DEVICE::dbg_Lock_VolCatInfo(const char *file, int line)
+{
+ bthread_mutex_lock_p(&volcat_mutex, file, line);
+}
+
+void DEVICE::dbg_Unlock_VolCatInfo(const char *file, int line)
+{
+ bthread_mutex_unlock_p(&volcat_mutex, file, line);
+}
+
+#else
+
+void DEVICE::Lock_acquire()
+{
+ P(acquire_mutex);
+}
+
+void DEVICE::Unlock_acquire()
+{
+ V(acquire_mutex);
+}
+
+void DEVICE::Lock_read_acquire()
+{
+ P(read_acquire_mutex);
+}
+
+void DEVICE::Unlock_read_acquire()
+{
+ V(read_acquire_mutex);
+}
+
+void DEVICE::Lock_VolCatInfo()
+{
+ P(volcat_mutex);
+}
+
+void DEVICE::Unlock_VolCatInfo()
+{
+ V(volcat_mutex);
+}
+
+
+
+#endif
+
+/* Main device access control */
+int DEVICE::init_mutex()
+{
+ return pthread_mutex_init(&m_mutex, NULL);
+}
+
+/* Mutex around the freespace command */
+int DEVICE::init_freespace_mutex()
+{
+ return pthread_mutex_init(&freespace_mutex, NULL);
+}
+
+/* Write device acquire mutex */
+int DEVICE::init_acquire_mutex()
+{
+ return pthread_mutex_init(&acquire_mutex, NULL);
+}
+
+/* Read device acquire mutex */
+int DEVICE::init_read_acquire_mutex()
+{
+ return pthread_mutex_init(&read_acquire_mutex, NULL);
+}
+
+/* VolCatInfo mutex */
+int DEVICE::init_volcat_mutex()
+{
+ return pthread_mutex_init(&volcat_mutex, NULL);
+}
+
+/* dcrs mutex */
+int DEVICE::init_dcrs_mutex()
+{
+ return pthread_mutex_init(&dcrs_mutex, NULL);
+}
+
+/* Set order in which device locks must be acquired */
+void DEVICE::set_mutex_priorities()
+{
+ /* Ensure that we respect this order in P/V operations */
+ bthread_mutex_set_priority(&m_mutex, PRIO_SD_DEV_ACCESS);
+ bthread_mutex_set_priority(&spool_mutex, PRIO_SD_DEV_SPOOL);
+ bthread_mutex_set_priority(&acquire_mutex, PRIO_SD_DEV_ACQUIRE);
+}
+
+int DEVICE::next_vol_timedwait(const struct timespec *timeout)
+{
+ return pthread_cond_timedwait(&wait_next_vol, &m_mutex, timeout);
+}
+
+
/*
* Block all other threads from using the device
* Device must already be locked. After this call,
- * the device is blocked to any thread calling dev->r_lock(),
+ * the device is blocked to any thread calling dev->rLock(),
* but the device is not locked (i.e. no P on device). Also,
- * the current thread can do slip through the dev->r_lock()
+ * the current thread can do slip through the dev->rLock()
* calls without blocking.
*/
void _block_device(const char *file, int line, DEVICE *dev, int state)
{
- ASSERT(dev->blocked() == BST_NOT_BLOCKED);
+ ASSERT2(dev->blocked() == BST_NOT_BLOCKED, "Block request of device already blocked");
dev->set_blocked(state); /* make other threads wait */
dev->no_wait_id = pthread_self(); /* allow us to continue */
- Dmsg3(sd_dbglvl, "set blocked=%s from %s:%d\n", dev->print_blocked(), file, line);
+ dev->blocked_by = get_jobid_from_tsd();
+ Dmsg4(sd_dbglvl, "Blocked %s %s from %s:%d\n",
+ dev->device->hdr.name, dev->print_blocked(), file, line);
}
/*
*/
void _unblock_device(const char *file, int line, DEVICE *dev)
{
- Dmsg3(sd_dbglvl, "unblock %s from %s:%d\n", dev->print_blocked(), file, line);
- ASSERT(dev->blocked());
+ Dmsg4(sd_dbglvl, "Unblocked %s %s from %s:%d\n", dev->device->hdr.name,
+ dev->print_blocked(), file, line);
+ ASSERT2(dev->blocked(), "Unblock request of device not blocked");
dev->set_blocked(BST_NOT_BLOCKED);
- dev->no_wait_id = 0;
+ dev->blocked_by = 0;
+ clear_thread_id(dev->no_wait_id);
if (dev->num_waiting > 0) {
pthread_cond_broadcast(&dev->wait); /* wake them up */
}
*/
void _steal_device_lock(const char *file, int line, DEVICE *dev, bsteal_lock_t *hold, int state)
{
-
- Dmsg3(sd_dbglvl, "steal lock. old=%s from %s:%d\n", dev->print_blocked(),
- file, line);
+ Dmsg4(sd_dbglvl, "Steal lock %s old=%s from %s:%d\n",
+ dev->device->hdr.name, dev->print_blocked(), file, line);
hold->dev_blocked = dev->blocked();
hold->dev_prev_blocked = dev->dev_prev_blocked;
hold->no_wait_id = dev->no_wait_id;
+ hold->blocked_by = dev->blocked_by;
dev->set_blocked(state);
Dmsg1(sd_dbglvl, "steal lock. new=%s\n", dev->print_blocked());
dev->no_wait_id = pthread_self();
- dev->dunlock();
+ dev->blocked_by = get_jobid_from_tsd();
+ dev->Unlock();
}
/*
*/
void _give_back_device_lock(const char *file, int line, DEVICE *dev, bsteal_lock_t *hold)
{
- Dmsg3(sd_dbglvl, "return lock. old=%s from %s:%d\n",
- dev->print_blocked(), file, line);
- dev->dlock();
+ Dmsg4(sd_dbglvl, "Return lock %s old=%s from %s:%d\n",
+ dev->device->hdr.name, dev->print_blocked(), file, line);
+ dev->Lock();
dev->set_blocked(hold->dev_blocked);
dev->dev_prev_blocked = hold->dev_prev_blocked;
dev->no_wait_id = hold->no_wait_id;
+ dev->blocked_by = hold->blocked_by;
Dmsg1(sd_dbglvl, "return lock. new=%s\n", dev->print_blocked());
if (dev->num_waiting > 0) {
pthread_cond_broadcast(&dev->wait); /* wake them up */
}
}
-const char *DEVICE::print_blocked() const
+const char *DEVICE::print_blocked() const
{
switch (m_blocked) {
case BST_NOT_BLOCKED:
return "BST_MOUNT";
case BST_DESPOOLING:
return "BST_DESPOOLING";
+ case BST_RELEASING:
+ return "BST_RELEASING";
default:
return _("unknown blocked code");
}
/*
* Check if the device is blocked or not
*/
-bool is_device_unmounted(DEVICE *dev)
+bool DEVICE::is_device_unmounted()
{
bool stat;
- int blocked = dev->blocked();
- stat = (blocked == BST_UNMOUNTED) ||
- (blocked == BST_UNMOUNTED_WAITING_FOR_SYSOP);
+
+ int blk = blocked();
+ stat = (blk == BST_UNMOUNTED) ||
+ (blk == BST_UNMOUNTED_WAITING_FOR_SYSOP);
return stat;
}