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

net.javacrumbs.shedlock.provider.mongo.reactivestreams.ReactiveStreamsMongoLockProvider Maven / Gradle / Ivy

/**
 * 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.reactivestreams; 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; import com.mongodb.MongoServerException; import com.mongodb.client.model.FindOneAndUpdateOptions; import com.mongodb.reactivestreams.client.MongoCollection; import com.mongodb.reactivestreams.client.MongoDatabase; import java.time.Instant; import java.util.Optional; 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.LockException; import net.javacrumbs.shedlock.support.Utils; import net.javacrumbs.shedlock.support.annotation.Nullable; import org.bson.Document; import org.bson.conversions.Bson; import org.reactivestreams.Publisher; /** * Distributed lock using Reactive MongoDB. Requires * mongodb-driver-reactivestreams * *

* 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. We will try to update lock record using filter _id == name AND lock_until * <= now *
  3. If the update succeeded (1 updated document), we have the lock. If the * update failed (0 updated documents) somebody else holds the lock *
  4. When unlocking, lock_until is set to now. *
*/ public class ReactiveStreamsMongoLockProvider 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 ReactiveStreamsMongoLockProvider(MongoDatabase mongoDatabase) { this(mongoDatabase.getCollection(DEFAULT_SHEDLOCK_COLLECTION_NAME)); } /** * Uses Mongo to coordinate locks * * @param collection * Mongo collection to be used */ public ReactiveStreamsMongoLockProvider(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 execute(getCollection() .findOneAndUpdate( and(eq(ID, lockConfiguration.getName()), lte(LOCK_UNTIL, now)), update, new FindOneAndUpdateOptions().upsert(true))); return Optional.of(new ReactiveMongoLock(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 = execute(getCollection() .findOneAndUpdate( and(eq(ID, lockConfiguration.getName()), gt(LOCK_UNTIL, now), eq(LOCKED_BY, hostname)), update)); if (updatedDocument != null) { return Optional.of(new ReactiveMongoLock(lockConfiguration, this)); } else { return Optional.empty(); } } private void unlock(LockConfiguration lockConfiguration) { // Set lockUtil to now or lockAtLeastUntil whichever is later execute(getCollection() .findOneAndUpdate( eq(ID, lockConfiguration.getName()), combine(set(LOCK_UNTIL, lockConfiguration.getUnlockTime())))); } @Nullable static T execute(Publisher command) { SingleLockableSubscriber subscriber = new SingleLockableSubscriber<>(); command.subscribe(subscriber); subscriber.await(); Throwable error = subscriber.getError(); if (error != null) { if (error instanceof RuntimeException) { throw (RuntimeException) error; } else { throw new LockException("Error when executing Mongo statement", error); } } else { return subscriber.getValue(); } } private MongoCollection getCollection() { return collection; } private Instant now() { return ClockProvider.now(); } private static final class ReactiveMongoLock extends AbstractSimpleLock { private final ReactiveStreamsMongoLockProvider mongoLockProvider; private ReactiveMongoLock( LockConfiguration lockConfiguration, ReactiveStreamsMongoLockProvider mongoLockProvider) { super(lockConfiguration); this.mongoLockProvider = mongoLockProvider; } @Override public void doUnlock() { mongoLockProvider.unlock(lockConfiguration); } @Override public Optional doExtend(LockConfiguration newLockConfiguration) { return mongoLockProvider.extend(newLockConfiguration); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy