All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.javacrumbs.shedlock.provider.mongo.MongoLockProvider Maven / Gradle / Ivy

There is a newer version: 6.0.2
Show newest version
/**
 * Copyright 2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.javacrumbs.shedlock.provider.mongo;

import com.mongodb.MongoServerException;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.FindOneAndUpdateOptions;
import net.javacrumbs.shedlock.core.AbstractSimpleLock;
import net.javacrumbs.shedlock.core.ClockProvider;
import net.javacrumbs.shedlock.core.ExtensibleLockProvider;
import net.javacrumbs.shedlock.core.LockConfiguration;
import net.javacrumbs.shedlock.core.SimpleLock;
import net.javacrumbs.shedlock.support.Utils;
import org.bson.Document;
import org.bson.conversions.Bson;

import java.time.Instant;
import java.util.Optional;

import static com.mongodb.client.model.Filters.and;
import static com.mongodb.client.model.Filters.eq;
import static com.mongodb.client.model.Filters.gt;
import static com.mongodb.client.model.Filters.lte;
import static com.mongodb.client.model.Updates.combine;
import static com.mongodb.client.model.Updates.set;

/**
 * Distributed lock using MongoDB >= 2.6. Requires mongo-java-driver > 3.4.0
 * 

* It uses a collection that contains documents like this: *

 * {
 *    "_id" : "lock name",
 *    "lockUntil" : ISODate("2017-01-07T16:52:04.071Z"),
 *    "lockedAt" : ISODate("2017-01-07T16:52:03.932Z"),
 *    "lockedBy" : "host name"
 * }
 * 
* * lockedAt and lockedBy are just for troubleshooting and are not read by the code * *
    *
  1. * Attempts to insert a new lock record. As an optimization, we keep in-memory track of created lock records. If the record * has been inserted, returns lock. *
  2. *
  3. * We will try to update lock record using filter _id == name AND lock_until <= now *
  4. *
  5. * If the update succeeded (1 updated document), we have the lock. If the update failed (0 updated documents) somebody else holds the lock *
  6. *
  7. * When unlocking, lock_until is set to now. *
  8. *
*/ public class MongoLockProvider implements ExtensibleLockProvider { static final String LOCK_UNTIL = "lockUntil"; static final String LOCKED_AT = "lockedAt"; static final String LOCKED_BY = "lockedBy"; static final String ID = "_id"; static final String DEFAULT_SHEDLOCK_COLLECTION_NAME = "shedLock"; private final String hostname; private final MongoCollection collection; /** * Uses Mongo to coordinate locks */ public MongoLockProvider(MongoDatabase mongoDatabase) { this(mongoDatabase.getCollection(DEFAULT_SHEDLOCK_COLLECTION_NAME)); } /** * Uses Mongo to coordinate locks * * @param collection Mongo collection to be used */ public MongoLockProvider(MongoCollection collection) { this.collection = collection; this.hostname = Utils.getHostname(); } @Override public Optional lock(LockConfiguration lockConfiguration) { Instant now = now(); Bson update = combine( set(LOCK_UNTIL, lockConfiguration.getLockAtMostUntil()), set(LOCKED_AT, now), set(LOCKED_BY, hostname) ); try { // There are three possible situations: // 1. The lock document does not exist yet - it is inserted - we have the lock // 2. The lock document exists and lockUtil <= now - it is updated - we have the lock // 3. The lock document exists and lockUtil > now - Duplicate key exception is thrown getCollection().findOneAndUpdate( and(eq(ID, lockConfiguration.getName()), lte(LOCK_UNTIL, now)), update, new FindOneAndUpdateOptions().upsert(true) ); return Optional.of(new MongoLock(lockConfiguration, this)); } catch (MongoServerException e) { if (e.getCode() == 11000) { // duplicate key //Upsert attempts to insert when there were no filter matches. //This means there was a lock with matching ID with lockUntil > now. return Optional.empty(); } else { throw e; } } } private Optional extend(LockConfiguration lockConfiguration) { Instant now = now(); Bson update = set(LOCK_UNTIL, lockConfiguration.getLockAtMostUntil()); Document updatedDocument = getCollection().findOneAndUpdate( and( eq(ID, lockConfiguration.getName()), gt(LOCK_UNTIL, now), eq(LOCKED_BY, hostname) ), update ); if (updatedDocument != null) { return Optional.of(new MongoLock(lockConfiguration, this)); } else { return Optional.empty(); } } private void unlock(LockConfiguration lockConfiguration) { // Set lockUtil to now or lockAtLeastUntil whichever is later getCollection().findOneAndUpdate( eq(ID, lockConfiguration.getName()), combine(set(LOCK_UNTIL, lockConfiguration.getUnlockTime())) ); } private MongoCollection getCollection() { return collection; } private Instant now() { return ClockProvider.now(); } private static final class MongoLock extends AbstractSimpleLock { private final MongoLockProvider mongoLockProvider; private MongoLock(LockConfiguration lockConfiguration, MongoLockProvider mongoLockProvider) { super(lockConfiguration); this.mongoLockProvider = mongoLockProvider; } @Override public void doUnlock() { mongoLockProvider.unlock(lockConfiguration); } @Override public Optional doExtend(LockConfiguration newLockConfiguration) { return mongoLockProvider.extend(newLockConfiguration); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy