mirror of
https://github.com/google/nomulus.git
synced 2025-05-15 17:07:15 +02:00
mv com/google/domain/registry google/registry
This change renames directories in preparation for the great package rename. The repository is now in a broken state because the code itself hasn't been updated. However this should ensure that git correctly preserves history for each file.
This commit is contained in:
parent
a41677aea1
commit
5012893c1d
2396 changed files with 0 additions and 0 deletions
253
java/google/registry/model/server/Lock.java
Normal file
253
java/google/registry/model/server/Lock.java
Normal file
|
@ -0,0 +1,253 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.model.server;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.Iterables.getFirst;
|
||||
import static com.google.common.collect.Iterables.skip;
|
||||
import static com.google.common.collect.Sets.newLinkedHashSet;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static com.google.domain.registry.util.CollectionUtils.nullToEmpty;
|
||||
import static com.google.domain.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static com.google.domain.registry.util.DateTimeUtils.isAtOrAfter;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.domain.registry.model.ImmutableObject;
|
||||
import com.google.domain.registry.model.annotations.NotBackedUp;
|
||||
import com.google.domain.registry.model.annotations.NotBackedUp.Reason;
|
||||
import com.google.domain.registry.util.AppEngineTimeLimiter;
|
||||
import com.google.domain.registry.util.FormattingLogger;
|
||||
|
||||
import com.googlecode.objectify.VoidWork;
|
||||
import com.googlecode.objectify.Work;
|
||||
import com.googlecode.objectify.annotation.Entity;
|
||||
import com.googlecode.objectify.annotation.Id;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Duration;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A lock on some shared resource. Locks are either specific to a tld or global to the entire
|
||||
* system, in which case a tld of null is used.
|
||||
*/
|
||||
@Entity
|
||||
@NotBackedUp(reason = Reason.TRANSIENT)
|
||||
public class Lock extends ImmutableObject {
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
|
||||
/** Fudge factor to make sure we kill threads before a lock actually expires. */
|
||||
private static final Duration LOCK_TIMEOUT_FUDGE = Duration.standardSeconds(5);
|
||||
|
||||
/** The name of the locked resource. */
|
||||
@Id
|
||||
String lockId;
|
||||
|
||||
/** When the lock can be considered implicitly released. */
|
||||
DateTime expirationTime;
|
||||
|
||||
/**
|
||||
* Insertion-ordered set of classes requesting access to the lock.
|
||||
*
|
||||
* <p>A class can only acquire the lock if the queue is empty or if it is at the top of the
|
||||
* queue. This allows us to prevent starvation between processes competing for the lock.
|
||||
*/
|
||||
LinkedHashSet<String> queue = new LinkedHashSet<>();
|
||||
|
||||
/**
|
||||
* Create a new {@link Lock} for the given resource name in the specified tld (which can be
|
||||
* null for cross-tld locks).
|
||||
*/
|
||||
private static Lock create(
|
||||
String resourceName,
|
||||
@Nullable String tld,
|
||||
DateTime expirationTime,
|
||||
LinkedHashSet<String> queue) {
|
||||
checkArgument(!Strings.isNullOrEmpty(resourceName), "resourceName cannot be null or empty");
|
||||
Lock instance = new Lock();
|
||||
// Add the tld to the Lock's id so that it is unique for locks acquiring the same resource
|
||||
// across different TLDs.
|
||||
instance.lockId = makeLockId(resourceName, tld);
|
||||
instance.expirationTime = expirationTime;
|
||||
instance.queue = queue;
|
||||
return instance;
|
||||
}
|
||||
|
||||
private static String makeLockId(String resourceName, @Nullable String tld) {
|
||||
return String.format("%s-%s", tld, resourceName);
|
||||
}
|
||||
|
||||
/** Join the queue waiting on this lock (unless you are already in the queue). */
|
||||
static void joinQueue(
|
||||
final Class<?> requester,
|
||||
final String resourceName,
|
||||
@Nullable final String tld) {
|
||||
// This transaction doesn't use the clock, so it's fine to use the default.
|
||||
ofy().transactNew(new VoidWork() {
|
||||
@Override
|
||||
public void vrun() {
|
||||
Lock lock = ofy().load().type(Lock.class).id(makeLockId(resourceName, tld)).now();
|
||||
LinkedHashSet<String> queue = (lock == null)
|
||||
? new LinkedHashSet<String>() : newLinkedHashSet(lock.queue);
|
||||
queue.add(requester.getCanonicalName());
|
||||
DateTime expirationTime = (lock == null) ? START_OF_TIME : lock.expirationTime;
|
||||
ofy().saveWithoutBackup().entity(create(resourceName, tld, expirationTime, queue));
|
||||
}});
|
||||
}
|
||||
|
||||
/** Try to acquire a lock. Returns null if it can't be acquired. */
|
||||
static Lock acquire(
|
||||
final Class<?> requester,
|
||||
final String resourceName,
|
||||
@Nullable final String tld,
|
||||
final Duration leaseLength) {
|
||||
// It's important to use transactNew rather than transact, because a Lock can be used to control
|
||||
// access to resources like GCS that can't be transactionally rolled back. Therefore, the lock
|
||||
// must be definitively acquired before it is used, even when called inside another transaction.
|
||||
return ofy().transactNew(new Work<Lock>() {
|
||||
@Override
|
||||
public Lock run() {
|
||||
Lock lock = ofy().load().type(Lock.class).id(makeLockId(resourceName, tld)).now();
|
||||
if (lock == null || isAtOrAfter(ofy().getTransactionTime(), lock.expirationTime)) {
|
||||
String requesterName = (requester == null) ? "" : requester.getCanonicalName();
|
||||
if (!getFirst(nullToEmpty((lock == null) ? null : lock.queue), requesterName)
|
||||
.equals(requesterName)) {
|
||||
// Another class is at the top of the queue; we can't acquire the lock.
|
||||
return null;
|
||||
}
|
||||
Lock newLock = create(
|
||||
resourceName,
|
||||
tld,
|
||||
ofy().getTransactionTime().plus(leaseLength),
|
||||
newLinkedHashSet((lock == null)
|
||||
? ImmutableList.<String>of() : skip(lock.queue, 1)));
|
||||
// Locks are not parented under an EntityGroupRoot (so as to avoid write contention) and
|
||||
// don't need to be backed up.
|
||||
ofy().saveWithoutBackup().entity(newLock);
|
||||
return newLock;
|
||||
}
|
||||
return null;
|
||||
}});
|
||||
}
|
||||
|
||||
/** Release the lock. */
|
||||
void release() {
|
||||
// Just use the default clock because we aren't actually doing anything that will use the clock.
|
||||
ofy().transact(new VoidWork() {
|
||||
@Override
|
||||
public void vrun() {
|
||||
// To release a lock, check that no one else has already obtained it and if not delete it.
|
||||
// If the lock in datastore was different then this lock is gone already; this can happen
|
||||
// if release() is called around the expiration time and the lock expires underneath us.
|
||||
Lock loadedLock = ofy().load().type(Lock.class).id(lockId).now();
|
||||
if (Lock.this.equals(loadedLock)) {
|
||||
// Use noBackupOfy() so that we don't create a commit log entry for deleting the lock.
|
||||
ofy().deleteWithoutBackup().entity(Lock.this);
|
||||
}
|
||||
}});
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquire one or more locks and execute a Void {@link Callable} on a thread that will be
|
||||
* killed if it doesn't complete before the lease expires.
|
||||
*
|
||||
* <p>If the requester isn't null, this will join each lock's queue before attempting to acquire
|
||||
* that lock. Clients that are concerned with starvation should specify a requester and those that
|
||||
* aren't shouldn't.
|
||||
*
|
||||
* <p>Note that locks are specific either to a given tld or to the entire system (in which case
|
||||
* tld should be passed as null).
|
||||
*
|
||||
* @return whether all locks were acquired and the callable was run.
|
||||
*/
|
||||
public static boolean executeWithLocks(
|
||||
final Callable<Void> callable,
|
||||
@Nullable Class<?> requester,
|
||||
@Nullable String tld,
|
||||
Duration leaseLength,
|
||||
String... lockNames) {
|
||||
try {
|
||||
return AppEngineTimeLimiter.create().callWithTimeout(
|
||||
new LockingCallable(
|
||||
callable, requester, Strings.emptyToNull(tld), leaseLength, lockNames),
|
||||
leaseLength.minus(LOCK_TIMEOUT_FUDGE).getMillis(),
|
||||
TimeUnit.MILLISECONDS,
|
||||
true);
|
||||
} catch (Exception e) {
|
||||
throw Throwables.propagate(e);
|
||||
}
|
||||
}
|
||||
|
||||
/** A {@link Callable} that acquires and releases a lock around a delegate {@link Callable}. */
|
||||
private static class LockingCallable implements Callable<Boolean> {
|
||||
final Callable<Void> delegate;
|
||||
final Class<?> requester;
|
||||
@Nullable final String tld;
|
||||
final Duration leaseLength;
|
||||
final Set<String> lockNames;
|
||||
|
||||
LockingCallable(
|
||||
Callable<Void> delegate,
|
||||
Class<?> requester,
|
||||
String tld,
|
||||
Duration leaseLength,
|
||||
String... lockNames) {
|
||||
checkArgument(leaseLength.isLongerThan(LOCK_TIMEOUT_FUDGE));
|
||||
this.delegate = delegate;
|
||||
this.requester = requester;
|
||||
this.tld = tld;
|
||||
this.leaseLength = leaseLength;
|
||||
// Make sure we join locks in a fixed (lexicographical) order to avoid deadlock.
|
||||
this.lockNames = ImmutableSortedSet.copyOf(lockNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean call() throws Exception {
|
||||
Set<Lock> acquiredLocks = new HashSet<>();
|
||||
try {
|
||||
for (String lockName : lockNames) {
|
||||
if (requester != null) {
|
||||
joinQueue(requester, lockName, tld);
|
||||
}
|
||||
Lock lock = acquire(requester, lockName, tld, leaseLength);
|
||||
if (lock == null) {
|
||||
logger.infofmt("Couldn't acquire lock: %s", lockName);
|
||||
return false;
|
||||
}
|
||||
logger.infofmt("Acquired lock: %s", lockName);
|
||||
acquiredLocks.add(lock);
|
||||
}
|
||||
delegate.call();
|
||||
return true;
|
||||
} finally {
|
||||
for (Lock lock : acquiredLocks) {
|
||||
lock.release();
|
||||
logger.infofmt("Released lock: %s", lock.lockId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
68
java/google/registry/model/server/ServerSecret.java
Normal file
68
java/google/registry/model/server/ServerSecret.java
Normal file
|
@ -0,0 +1,68 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.model.server;
|
||||
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static com.google.domain.registry.model.ofy.Ofy.RECOMMENDED_MEMCACHE_EXPIRATION;
|
||||
|
||||
import com.google.domain.registry.model.annotations.NotBackedUp;
|
||||
import com.google.domain.registry.model.annotations.NotBackedUp.Reason;
|
||||
import com.google.domain.registry.model.common.CrossTldSingleton;
|
||||
|
||||
import com.googlecode.objectify.VoidWork;
|
||||
import com.googlecode.objectify.annotation.Cache;
|
||||
import com.googlecode.objectify.annotation.Entity;
|
||||
import com.googlecode.objectify.annotation.Unindex;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/** A secret number used for generating tokens (such as XSRF tokens). */
|
||||
@Entity
|
||||
@Cache(expirationSeconds = RECOMMENDED_MEMCACHE_EXPIRATION)
|
||||
@Unindex
|
||||
@NotBackedUp(reason = Reason.AUTO_GENERATED)
|
||||
public class ServerSecret extends CrossTldSingleton {
|
||||
|
||||
private static ServerSecret secret;
|
||||
|
||||
long mostSignificant;
|
||||
long leastSignificant;
|
||||
|
||||
/**
|
||||
* Get the server secret, creating it if the datastore doesn't have one already.
|
||||
* <p>
|
||||
* There's a tiny risk of a race here if two calls to this happen simultaneously and create
|
||||
* different keys, in which case one of the calls will end up with an incorrect key. However, this
|
||||
* happens precisely once in the history of the system (after that it's always in datastore) so
|
||||
* it's not worth worrying about.
|
||||
*/
|
||||
public static UUID getServerSecret() {
|
||||
if (secret == null) {
|
||||
secret = ofy().load().entity(new ServerSecret()).now();
|
||||
}
|
||||
if (secret == null) {
|
||||
secret = new ServerSecret();
|
||||
UUID uuid = UUID.randomUUID();
|
||||
secret.mostSignificant = uuid.getMostSignificantBits();
|
||||
secret.leastSignificant = uuid.getLeastSignificantBits();
|
||||
ofy().transact(new VoidWork(){
|
||||
@Override
|
||||
public void vrun() {
|
||||
ofy().saveWithoutBackup().entity(secret);
|
||||
}});
|
||||
}
|
||||
return new UUID(secret.mostSignificant, secret.leastSignificant);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue