diff --git a/includes/admin.form.inc b/includes/admin.form.inc
index 9f64cb6d..18b9bb4e 100644
--- a/includes/admin.form.inc
+++ b/includes/admin.form.inc
@@ -61,6 +61,23 @@ function islandora_repository_admin(array $form, array &$form_state) {
'#description' => t('HTTP caching can reduce network traffic, by allowing clients to used cached copies.'),
'#default_value' => variable_get('islandora_use_datastream_cache_headers', TRUE),
),
+ 'islandora_use_object_semaphores' => array(
+ '#type' => 'checkbox',
+ '#title' => t('Make Processes Claim Objects for Modification'),
+ '#description' => t('Enabling this will increase stability of Fedora at high concurrency but will incur a heavy performance hit.'),
+ '#default_value' => variable_get('islandora_use_object_semaphores', FALSE),
+ ),
+ 'islandora_semaphore_period' => array(
+ '#type' => 'textfield',
+ '#title' => t('Time to Claim Objects for'),
+ '#default_value' => variable_get('islandora_semaphore_period', 600),
+ '#description' => t('Maximum time in seconds to claim objects for modification.'),
+ '#states' => array(
+ 'invisible' => array(
+ ':input[name="islandora_use_object_semaphores"]' => array('checked' => FALSE),
+ ),
+ ),
+ ),
'islandora_defer_derivatives_on_ingest' => array(
'#type' => 'checkbox',
'#title' => t('Defer derivative generation during ingest'),
@@ -123,6 +140,21 @@ function islandora_repository_admin(array $form, array &$form_state) {
return system_settings_form($form);
}
+/**
+ * Validate the admin form.
+ */
+function islandora_repository_admin_validate($form, &$form_state) {
+ // Only validate semaphore period if semaphores are enabled.
+ if ($form_state['values']['islandora_use_object_semaphores']) {
+ if ($form_state['values']['islandora_semaphore_period']) {
+ element_validate_integer_positive($form['islandora_tabs']['islandora_general']['islandora_semaphore_period'], $form_state);
+ }
+ else {
+ form_set_error('islandora_semaphore_period', t('Time to Claim Objects for must not be empty if Make Processes Claim Objects for Modification is checked.'));
+ }
+ }
+}
+
/**
* Gets a message which describes if the repository is accessible.
*
diff --git a/includes/tuque_wrapper.inc b/includes/tuque_wrapper.inc
index e7ee75c2..f075c402 100644
--- a/includes/tuque_wrapper.inc
+++ b/includes/tuque_wrapper.inc
@@ -371,7 +371,7 @@ class IslandoraFedoraApiM extends FedoraApiM {
if ($context['block']) {
throw new Exception('Modify Datastream was blocked.');
}
- return parent::modifyDatastream($pid, $dsid, $params);
+ return $this->callParentWithLocking('modifyDatastream', $pid, $pid, $dsid, $params);
}
/**
@@ -391,7 +391,7 @@ class IslandoraFedoraApiM extends FedoraApiM {
if ($context['block']) {
throw new Exception('Modify Object was blocked.');
}
- return parent::modifyObject($pid, $params);
+ return $this->callParentWithLocking('modifyObject', $pid, $pid, $params);
}
/**
@@ -422,7 +422,7 @@ class IslandoraFedoraApiM extends FedoraApiM {
return '';
default:
- $ret = parent::purgeObject($pid, $log_message);
+ $ret = $this->callParentWithLocking('purgeObject', $pid, $pid, $log_message);
islandora_invoke_object_hooks(ISLANDORA_OBJECT_PURGED_HOOK, $models, $pid);
return $ret;
}
@@ -436,6 +436,87 @@ class IslandoraFedoraApiM extends FedoraApiM {
}
}
+ /**
+ * Wraps purgeDatastream for semaphore locking.
+ *
+ * @see FedoraApiM::purgeDatastream
+ */
+ public function purgeDatastream($pid, $dsid, $params = array()) {
+ return $this->callParentWithLocking('purgeDatastream', $pid, $pid, $dsid, $params);
+ }
+
+ /**
+ * Wraps ingest for semaphore locking.
+ *
+ * @see FedoraApiM::ingest
+ */
+ public function ingest($params = array()) {
+ if (isset($params['pid'])) {
+ return $this->callParentWithLocking('ingest', $params['pid'], $params);
+ }
+ else {
+ return parent::ingest($params);
+ }
+ }
+
+ /**
+ * Wraps addDatastream for semaphore locking.
+ *
+ * @see FedoraApiM::addDatastream
+ */
+ public function addDatastream($pid, $dsid, $type, $file, $params) {
+ return $this->callParentWithLocking('addDatastream', $pid, $pid, $dsid, $type, $file, $params);
+ }
+
+ /**
+ * Wraps addRelationship for semaphore locking.
+ *
+ * @see FedoraApiM::addRelationship
+ */
+ public function addRelationship($pid, $relationship, $is_literal, $datatype = NULL) {
+ return $this->callParentWithLocking('addRelationship', $pid, $pid, $relationship, $is_literal, $datatype);
+ }
+
+ /**
+ * Call a parent function while using semaphores as configured.
+ *
+ * All extra arguments are passed along to the callback.
+ *
+ * @param callable $callback
+ * The method we are wrapping.
+ * @param string $pid
+ * The PID to create a semaphore for.
+ */
+ protected function callParentWithLocking($callback, $pid) {
+ $args = array_slice(func_get_args(), 2);
+ $locked = FALSE;
+
+ if (variable_get('islandora_use_object_semaphores', FALSE)) {
+ $lock_period = variable_get('islandora_semaphore_period', 600);
+ while (!lock_acquire($pid, $lock_period)) {
+ // Wait for the lock to be free. In the worst case forever.
+ while (lock_wait($pid)) {
+ }
+ }
+ $locked = TRUE;
+ }
+
+ if ($locked) {
+ try {
+ $to_return = call_user_func_array(array($this, "parent::$callback"), $args);
+ }
+ catch (Exception $e) {
+ // Release the lock in event of exception.
+ lock_release($pid);
+ throw $e;
+ }
+ lock_release($pid);
+ return $to_return;
+ }
+ else {
+ return call_user_func_array(array($this, "parent::$callback"), $args);
+ }
+ }
}
class IslandoraSimpleCache extends SimpleCache {}
diff --git a/islandora.install b/islandora.install
index 075e81a2..e1dc340d 100644
--- a/islandora.install
+++ b/islandora.install
@@ -55,6 +55,8 @@ function islandora_uninstall() {
'islandora_namespace_restriction_enforced',
'islandora_pids_allowed',
'islandora_risearch_use_itql_when_necessary',
+ 'islandora_use_object_semaphores',
+ 'islandora_semaphore_period',
);
array_walk($variables, 'variable_del');
}