Fix server startup when replication.config exists

The addition of the per-request RefControl cache broke push
replication as the PushOp "request" could not locate a Guice
binding for the cache.

Use the same trick we do with MergeOp to setup a per-operation
"request" scope for Guice.

Change-Id: I0cecbad4809493f43cb2fec3624b1a53e38fb48f
This commit is contained in:
Shawn O. Pearce
2011-06-24 16:18:39 -07:00
parent 6a44b81bb5
commit 2353f8f5b5
4 changed files with 115 additions and 83 deletions

View File

@@ -22,16 +22,13 @@ import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.RemotePeer; import com.google.gerrit.server.RemotePeer;
import com.google.gerrit.server.RequestCleanup;
import com.google.gerrit.server.config.GerritRequestModule; import com.google.gerrit.server.config.GerritRequestModule;
import com.google.gerrit.server.ssh.SshInfo; import com.google.gerrit.server.ssh.SshInfo;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Injector; import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.OutOfScopeException; import com.google.inject.OutOfScopeException;
import com.google.inject.Provider; import com.google.inject.Provider;
import com.google.inject.Scope;
import com.google.inject.servlet.RequestScoped; import com.google.inject.servlet.RequestScoped;
import com.jcraft.jsch.HostKey; import com.jcraft.jsch.HostKey;
@@ -65,7 +62,7 @@ public class ChangeMergeQueue implements MergeQueue {
Injector child = parent.createChildInjector(new AbstractModule() { Injector child = parent.createChildInjector(new AbstractModule() {
@Override @Override
protected void configure() { protected void configure() {
bindScope(RequestScoped.class, MyScope.REQUEST); bindScope(RequestScoped.class, PerThreadRequestScope.REQUEST);
install(new GerritRequestModule()); install(new GerritRequestModule());
bind(CurrentUser.class).to(IdentifiedUser.class); bind(CurrentUser.class).to(IdentifiedUser.class);
@@ -186,8 +183,8 @@ public class ChangeMergeQueue implements MergeQueue {
private void mergeImpl(Branch.NameKey branch) { private void mergeImpl(Branch.NameKey branch) {
try { try {
MyScope ctx = new MyScope(); PerThreadRequestScope ctx = new PerThreadRequestScope();
MyScope old = MyScope.set(ctx); PerThreadRequestScope old = PerThreadRequestScope.set(ctx);
try { try {
try { try {
bgFactory.get().create(branch).merge(); bgFactory.get().create(branch).merge();
@@ -195,7 +192,7 @@ public class ChangeMergeQueue implements MergeQueue {
ctx.cleanup.run(); ctx.cleanup.run();
} }
} finally { } finally {
MyScope.set(old); PerThreadRequestScope.set(old);
} }
} catch (Throwable e) { } catch (Throwable e) {
log.error("Merge attempt for " + branch + " failed", e); log.error("Merge attempt for " + branch + " failed", e);
@@ -261,65 +258,4 @@ public class ChangeMergeQueue implements MergeQueue {
return "recheck " + project.get() + " " + dest.getShortName(); return "recheck " + project.get() + " " + dest.getShortName();
} }
} }
private static class MyScope {
private static final ThreadLocal<MyScope> current =
new ThreadLocal<MyScope>();
private static MyScope getContext() {
final MyScope ctx = current.get();
if (ctx == null) {
throw new OutOfScopeException("Not in command/request");
}
return ctx;
}
static MyScope set(MyScope ctx) {
MyScope old = current.get();
current.set(ctx);
return old;
}
static final Scope REQUEST = new Scope() {
public <T> Provider<T> scope(final Key<T> key, final Provider<T> creator) {
return new Provider<T>() {
public T get() {
return getContext().get(key, creator);
}
@Override
public String toString() {
return String.format("%s[%s]", creator, REQUEST);
}
};
}
@Override
public String toString() {
return "MergeQueue.REQUEST";
}
};
private static final Key<RequestCleanup> RC_KEY =
Key.get(RequestCleanup.class);
private final RequestCleanup cleanup;
private final Map<Key<?>, Object> map;
MyScope() {
cleanup = new RequestCleanup();
map = new HashMap<Key<?>, Object>();
map.put(RC_KEY, cleanup);
}
synchronized <T> T get(Key<T> key, Provider<T> creator) {
@SuppressWarnings("unchecked")
T t = (T) map.get(key);
if (t == null) {
t = creator.get();
map.put(key, t);
}
return t;
}
}
} }

View File

@@ -0,0 +1,85 @@
// Copyright (C) 2009 The Android Open Source Project
//
// 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.gerrit.server.git;
import com.google.gerrit.server.RequestCleanup;
import com.google.inject.Key;
import com.google.inject.OutOfScopeException;
import com.google.inject.Provider;
import com.google.inject.Scope;
import java.util.HashMap;
import java.util.Map;
class PerThreadRequestScope {
private static final ThreadLocal<PerThreadRequestScope> current =
new ThreadLocal<PerThreadRequestScope>();
private static PerThreadRequestScope getContext() {
final PerThreadRequestScope ctx = current.get();
if (ctx == null) {
throw new OutOfScopeException("Not in command/request");
}
return ctx;
}
static PerThreadRequestScope set(PerThreadRequestScope ctx) {
PerThreadRequestScope old = current.get();
current.set(ctx);
return old;
}
static final Scope REQUEST = new Scope() {
public <T> Provider<T> scope(final Key<T> key, final Provider<T> creator) {
return new Provider<T>() {
public T get() {
return getContext().get(key, creator);
}
@Override
public String toString() {
return String.format("%s[%s]", creator, REQUEST);
}
};
}
@Override
public String toString() {
return "PerThreadRequestScope.REQUEST";
}
};
private static final Key<RequestCleanup> RC_KEY =
Key.get(RequestCleanup.class);
final RequestCleanup cleanup;
private final Map<Key<?>, Object> map;
PerThreadRequestScope() {
cleanup = new RequestCleanup();
map = new HashMap<Key<?>, Object>();
map.put(RC_KEY, cleanup);
}
synchronized <T> T get(Key<T> key, Provider<T> creator) {
@SuppressWarnings("unchecked")
T t = (T) map.get(key);
if (t == null) {
t = creator.get();
map.put(key, t);
}
return t;
}
}

View File

@@ -155,6 +155,16 @@ class PushOp implements ProjectRunnable {
} }
public void run() { public void run() {
PerThreadRequestScope ctx = new PerThreadRequestScope();
PerThreadRequestScope old = PerThreadRequestScope.set(ctx);
try {
runPushOperation();
} finally {
PerThreadRequestScope.set(old);
}
}
private void runPushOperation() {
// Lock the queue, and remove ourselves, so we can't be modified once // Lock the queue, and remove ourselves, so we can't be modified once
// we start replication (instead a new instance, with the same URI, is // we start replication (instead a new instance, with the same URI, is
// created and scheduled for a future point in time.) // created and scheduled for a future point in time.)

View File

@@ -22,6 +22,7 @@ import com.google.gerrit.server.ReplicationUser;
import com.google.gerrit.server.config.ConfigUtil; import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.SitePaths; import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.project.NoSuchProjectException; import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.PerRequestProjectControlCache;
import com.google.gerrit.server.project.ProjectControl; import com.google.gerrit.server.project.ProjectControl;
import com.google.gwtorm.client.SchemaFactory; import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
@@ -29,6 +30,7 @@ import com.google.inject.Inject;
import com.google.inject.Injector; import com.google.inject.Injector;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import com.google.inject.assistedinject.FactoryProvider; import com.google.inject.assistedinject.FactoryProvider;
import com.google.inject.servlet.RequestScoped;
import com.jcraft.jsch.Session; import com.jcraft.jsch.Session;
@@ -373,6 +375,8 @@ public class PushReplication implements ReplicationQueue {
injector.createChildInjector(new AbstractModule() { injector.createChildInjector(new AbstractModule() {
@Override @Override
protected void configure() { protected void configure() {
bindScope(RequestScoped.class, PerThreadRequestScope.REQUEST);
bind(PerRequestProjectControlCache.class).in(RequestScoped.class);
bind(CurrentUser.class).toInstance(remoteUser); bind(CurrentUser.class).toInstance(remoteUser);
} }
}).getInstance(ProjectControl.Factory.class); }).getInstance(ProjectControl.Factory.class);
@@ -396,15 +400,22 @@ public class PushReplication implements ReplicationQueue {
void schedule(final Project.NameKey project, final String ref, void schedule(final Project.NameKey project, final String ref,
final URIish uri) { final URIish uri) {
PerThreadRequestScope ctx = new PerThreadRequestScope();
PerThreadRequestScope old = PerThreadRequestScope.set(ctx);
try { try {
if (!controlFor(project).isVisible()) { try {
if (!controlFor(project).isVisible()) {
return;
}
} catch (NoSuchProjectException e1) {
log.error("Internal error: project " + project
+ " not found during replication");
return; return;
} }
} catch (NoSuchProjectException e1) { } finally {
log.error("Internal error: project " + project PerThreadRequestScope.set(old);
+ " not found during replication");
return;
} }
synchronized (pending) { synchronized (pending) {
PushOp e = pending.get(uri); PushOp e = pending.get(uri);
if (e == null) { if (e == null) {
@@ -443,16 +454,6 @@ public class PushReplication implements ReplicationQueue {
* @param pushOp The PushOp instance to be scheduled. * @param pushOp The PushOp instance to be scheduled.
*/ */
void reschedule(final PushOp pushOp) { void reschedule(final PushOp pushOp) {
try {
if (!controlFor(pushOp.getProjectNameKey()).isVisible()) {
return;
}
} catch (NoSuchProjectException e1) {
log.error("Internal error: project " + pushOp.getProjectNameKey()
+ " not found during replication");
return;
}
// It locks access to pending variable. // It locks access to pending variable.
synchronized (pending) { synchronized (pending) {
URIish uri = pushOp.getURI(); URIish uri = pushOp.getURI();