提交 5ab06c4a 编写于 作者: K Kohsuke Kawaguchi

Merge remote-tracking branch 'origin/master' into lazy-load

......@@ -55,6 +55,12 @@ Upcoming changes</a>
<!-- Record your changes in the trunk here. -->
<div id="trunk" style="display:none"><!--=TRUNK-BEGIN=-->
<ul class=image>
<li class=bug>
Log recorders do not work reliably.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-15226">issue 15226</a>)
<li class=bug>
<code>FilePath.validateAntFileMask</code> too slow for <code>/configure</code>.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-7214">issue 7214</a>)
<li class=>
</ul>
</div><!--=TRUNK-END=-->
......
......@@ -414,7 +414,7 @@ public abstract class ExtensionFinder implements ExtensionPoint {
* Instead, we should just drop the failing plugins.
*/
public static final Scope FAULT_TOLERANT_SCOPE = new Scope() {
public <T> Provider<T> scope(Key<T> key, Provider<T> unscoped) {
public <T> Provider<T> scope(final Key<T> key, final Provider<T> unscoped) {
final Provider<T> base = Scopes.SINGLETON.scope(key,unscoped);
return new Provider<T>() {
public T get() {
......@@ -423,6 +423,9 @@ public abstract class ExtensionFinder implements ExtensionPoint {
} catch (Exception e) {
LOGGER.log(Level.WARNING,"Failed to instantiate. Skipping this component",e);
return null;
} catch (LinkageError e) {
LOGGER.log(Level.WARNING,"Failed to instantiate. Skipping this component",e);
return null;
}
}
};
......
......@@ -27,7 +27,6 @@ package hudson;
import hudson.Launcher.LocalLauncher;
import hudson.Launcher.RemoteLauncher;
import hudson.model.AbstractDescribableImpl;
import jenkins.model.Jenkins;
import hudson.model.TaskListener;
import hudson.model.AbstractProject;
......@@ -840,7 +839,7 @@ public final class FilePath implements Serializable {
return channel.call(wrapper);
} catch (TunneledInterruptedException e) {
throw (InterruptedException)new InterruptedException().initCause(e);
throw (InterruptedException)new InterruptedException(e.getMessage()).initCause(e);
} catch (AbortException e) {
throw e; // pass through so that the caller can catch it as AbortException
} catch (IOException e) {
......@@ -1976,8 +1975,26 @@ public final class FilePath implements Serializable {
* @see #validateFileMask(FilePath, String)
*/
public String validateAntFileMask(final String fileMasks) throws IOException, InterruptedException {
return validateAntFileMask(fileMasks, Integer.MAX_VALUE);
}
/**
* Like {@link #validateAntFileMask(String)} but performing only a bounded number of operations.
* <p>Whereas the unbounded overload is appropriate for calling from cancelable, long-running tasks such as build steps,
* this overload should be used when an answer is needed quickly, such as for {@link #validateFileMask(String)}
* or anything else returning {@link FormValidation}.
* <p>If a positive match is found, {@code null} is returned immediately.
* A message is returned in case the file pattern can definitely be determined to not match anything in the directory within the alloted time.
* If the time runs out without finding a match but without ruling out the possibility that there might be one, {@link InterruptedException} is thrown,
* in which case the calling code should give the user the benefit of the doubt and use {@link hudson.util.FormValidation.Kind#OK} (with or without a message).
* @param bound a maximum number of negative operations (deliberately left vague) to perform before giving up on a precise answer; 10_000 is a reasonable pick
* @throws InterruptedException not only in case of a channel failure, but also if too many operations were performed without finding any matches
* @since 1.484
*/
public String validateAntFileMask(final String fileMasks, final int bound) throws IOException, InterruptedException {
return act(new FileCallable<String>() {
public String invoke(File dir, VirtualChannel channel) throws IOException {
private static final long serialVersionUID = 1;
public String invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
if(fileMasks.startsWith("~"))
return Messages.FilePath_TildaDoesntWork();
......@@ -2074,10 +2091,28 @@ public final class FilePath implements Serializable {
return null; // no error
}
private boolean hasMatch(File dir, String pattern) {
FileSet fs = Util.createFileSet(dir,pattern);
DirectoryScanner ds = fs.getDirectoryScanner(new Project());
private boolean hasMatch(File dir, String pattern) throws InterruptedException {
class Cancel extends RuntimeException {}
DirectoryScanner ds = bound == Integer.MAX_VALUE ? new DirectoryScanner() : new DirectoryScanner() {
int ticks;
@Override public synchronized boolean isCaseSensitive() {
if (!filesIncluded.isEmpty() || !dirsIncluded.isEmpty() || ticks++ > bound) {
throw new Cancel();
}
return super.isCaseSensitive();
}
};
ds.setBasedir(dir);
ds.setIncludes(new String[] {pattern});
try {
ds.scan();
} catch (Cancel c) {
if (ds.getIncludedFilesCount()!=0 || ds.getIncludedDirsCount()!=0) {
return true;
} else {
throw new InterruptedException("no matches found within " + bound);
}
}
return ds.getIncludedFilesCount()!=0 || ds.getIncludedDirsCount()!=0;
}
......@@ -2126,11 +2161,11 @@ public final class FilePath implements Serializable {
if(!exists()) // no workspace. can't check
return FormValidation.ok();
String msg = validateAntFileMask(value);
String msg = validateAntFileMask(value, 10000);
if(errorIfNotExist) return FormValidation.error(msg);
else return FormValidation.warning(msg);
} catch (InterruptedException e) {
return FormValidation.ok();
return FormValidation.ok(Messages.FilePath_did_not_manage_to_validate_may_be_too_sl(value));
}
}
......
......@@ -87,6 +87,7 @@ public class LogRecorder extends AbstractModelObject implements Saveable {
public static final class Target {
public final String name;
private final int level;
private transient /* almost final*/ Logger logger;
public Target(String name, Level level) {
this(name,level.intValue());
......@@ -118,7 +119,10 @@ public class LogRecorder extends AbstractModelObject implements Saveable {
}
public Logger getLogger() {
return Logger.getLogger(name);
if (logger == null) {
logger = Logger.getLogger(name);
}
return logger;
}
/**
......
......@@ -30,6 +30,7 @@ import hudson.triggers.Trigger;
import jenkins.model.Jenkins;
import java.util.Random;
import java.util.Timer;
import java.util.logging.Logger;
......@@ -84,7 +85,10 @@ public abstract class AperiodicWork extends SafeTimerTask implements ExtensionPo
@Override
public final void doRun() throws Exception{
doAperiodicRun();
Trigger.timer.schedule(getNewInstance(), getRecurrencePeriod());
Timer timer = Trigger.timer;
if (timer != null) {
timer.schedule(getNewInstance(), getRecurrencePeriod());
}
}
protected abstract void doAperiodicRun();
......
......@@ -85,6 +85,7 @@ import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.Timer;
import java.util.TreeSet;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicInteger;
......@@ -93,7 +94,6 @@ import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.management.timer.Timer;
import javax.servlet.ServletException;
import jenkins.model.Jenkins;
......@@ -1746,8 +1746,11 @@ public class Queue extends ResourceController implements Saveable {
MaintainTask(Queue queue) {
this.queue = new WeakReference<Queue>(queue);
long interval = 5 * Timer.ONE_SECOND;
Trigger.timer.schedule(this, interval, interval);
long interval = 5000;
Timer timer = Trigger.timer;
if (timer != null) {
timer.schedule(this, interval, interval);
}
}
protected void doRun() {
......
......@@ -32,12 +32,8 @@ import hudson.Extension;
import hudson.ExtensionPoint;
import hudson.Indenter;
import hudson.Util;
import hudson.XmlFile;
import hudson.model.Descriptor.FormException;
import hudson.model.Node.Mode;
import hudson.model.labels.LabelAtom;
import hudson.model.labels.LabelAtomPropertyDescriptor;
import hudson.model.listeners.SaveableListener;
import hudson.scm.ChangeLogSet.Entry;
import hudson.search.CollectionSearchIndex;
import hudson.search.SearchIndexBuilder;
......@@ -48,11 +44,9 @@ import hudson.security.PermissionGroup;
import hudson.security.PermissionScope;
import hudson.util.AlternativeUiTextProvider;
import hudson.util.AlternativeUiTextProvider.Message;
import hudson.util.AtomicFileWriter;
import hudson.util.DescribableList;
import hudson.util.DescriptorList;
import hudson.util.IOException2;
import hudson.util.IOUtils;
import hudson.util.RunList;
import hudson.util.XStream2;
import hudson.views.ListViewColumn;
......@@ -77,7 +71,6 @@ import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
......@@ -92,7 +85,6 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
......@@ -645,7 +637,7 @@ public abstract class View extends AbstractModelObject implements AccessControll
@Exported
public final List<UserInfo> users;
public final Object parent;
public final ModelObject parent;
public People(Jenkins parent) {
this.parent = parent;
......
......@@ -36,6 +36,7 @@ import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.logging.Level;
import java.util.logging.Logger;
......@@ -70,11 +71,14 @@ public abstract class AbstractNodeMonitorDescriptor<T> extends Descriptor<Node
}
private void schedule(long interval) {
Trigger.timer.scheduleAtFixedRate(new SafeTimerTask() {
public void doRun() {
triggerUpdate();
}
}, interval, interval);
Timer timer = Trigger.timer;
if (timer != null) {
timer.scheduleAtFixedRate(new SafeTimerTask() {
public void doRun() {
triggerUpdate();
}
}, interval, interval);
}
}
/**
......
......@@ -62,6 +62,8 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import antlr.ANTLRException;
import javax.annotation.CheckForNull;
import edu.umd.cs.findbugs.annotations.SuppressWarnings;
/**
* Triggers a {@link Build}.
......@@ -278,26 +280,32 @@ public abstract class Trigger<J extends Item> implements Describable<Trigger<?>>
*
* If plugins want to run periodic jobs, they should implement {@link PeriodicWork}.
*/
public static Timer timer;
@SuppressWarnings("MS_SHOULD_BE_FINAL")
public static @CheckForNull Timer timer;
@Initializer(after=JOB_LOADED)
public static void init() {
new DoubleLaunchChecker().schedule();
// start all PeridocWorks
for(PeriodicWork p : PeriodicWork.all())
timer.scheduleAtFixedRate(p,p.getInitialDelay(),p.getRecurrencePeriod());
// start all AperidocWorks
for(AperiodicWork p : AperiodicWork.all())
timer.schedule(p,p.getInitialDelay());
// start monitoring nodes, although there's no hurry.
timer.schedule(new SafeTimerTask() {
public void doRun() {
ComputerSet.initialize();
Timer _timer = timer;
if (_timer != null) {
// start all PeridocWorks
for(PeriodicWork p : PeriodicWork.all()) {
_timer.scheduleAtFixedRate(p,p.getInitialDelay(),p.getRecurrencePeriod());
}
}, 1000*10);
// start all AperidocWorks
for(AperiodicWork p : AperiodicWork.all()) {
_timer.schedule(p,p.getInitialDelay());
}
// start monitoring nodes, although there's no hurry.
_timer.schedule(new SafeTimerTask() {
public void doRun() {
ComputerSet.initialize();
}
}, 1000*10);
}
}
/**
......
......@@ -40,6 +40,7 @@ import java.util.logging.Logger;
import java.util.logging.Level;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Method;
import java.util.Timer;
/**
* Makes sure that no other Hudson uses our <tt>JENKINS_HOME</tt> directory,
......@@ -145,11 +146,14 @@ public class DoubleLaunchChecker {
public void schedule() {
// randomize the scheduling so that multiple Hudson instances will write at the file at different time
long MINUTE = 1000*60;
Trigger.timer.schedule(new SafeTimerTask() {
protected void doRun() {
execute();
}
},(random.nextInt(30)+60)*MINUTE);
Timer timer = Trigger.timer;
if (timer != null) {
timer.schedule(new SafeTimerTask() {
protected void doRun() {
execute();
}
},(random.nextInt(30)+60)*MINUTE);
}
}
/**
......
......@@ -378,11 +378,11 @@ public abstract class FormFieldValidator {
return;
}
String msg = ws.validateAntFileMask(value);
String msg = ws.validateAntFileMask(value, 10000);
if(errorIfNotExist) error(msg);
else warning(msg);
} catch (InterruptedException e) {
ok(); // coundn't check
ok(Messages.FormFieldValidator_did_not_manage_to_validate_may_be_too_sl(value));
}
}
......
......@@ -727,7 +727,10 @@ public class Jenkins extends AbstractCIBase implements ModifiableTopLevelItemGro
* @param pluginManager
* If non-null, use existing plugin manager. create a new one.
*/
@edu.umd.cs.findbugs.annotations.SuppressWarnings("SC_START_IN_CTOR") // bug in FindBugs. It flags UDPBroadcastThread.start() call but that's for another class
@edu.umd.cs.findbugs.annotations.SuppressWarnings({
"SC_START_IN_CTOR", // bug in FindBugs. It flags UDPBroadcastThread.start() call but that's for another class
"ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD" // Trigger.timer
})
protected Jenkins(File root, ServletContext context, PluginManager pluginManager) throws IOException, InterruptedException, ReactorException {
long start = System.currentTimeMillis();
......@@ -816,12 +819,15 @@ public class Jenkins extends AbstractCIBase implements ModifiableTopLevelItemGro
}
dnsMultiCast = new DNSMultiCast(this);
Trigger.timer.scheduleAtFixedRate(new SafeTimerTask() {
@Override
protected void doRun() throws Exception {
trimLabels();
}
}, TimeUnit2.MINUTES.toMillis(5), TimeUnit2.MINUTES.toMillis(5));
Timer timer = Trigger.timer;
if (timer != null) {
timer.scheduleAtFixedRate(new SafeTimerTask() {
@Override
protected void doRun() throws Exception {
trimLabels();
}
}, TimeUnit2.MINUTES.toMillis(5), TimeUnit2.MINUTES.toMillis(5));
}
updateComputerList();
......@@ -2563,6 +2569,7 @@ public class Jenkins extends AbstractCIBase implements ModifiableTopLevelItemGro
/**
* Called to shut down the system.
*/
@edu.umd.cs.findbugs.annotations.SuppressWarnings("ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD")
public void cleanUp() {
for (ItemListener l : ItemListener.all())
l.onBeforeShutdown();
......@@ -2579,7 +2586,10 @@ public class Jenkins extends AbstractCIBase implements ModifiableTopLevelItemGro
if(dnsMultiCast!=null)
dnsMultiCast.close();
interruptReloadThread();
Trigger.timer.cancel();
Timer timer = Trigger.timer;
if (timer != null) {
timer.cancel();
}
// TODO: how to wait for the completion of the last job?
Trigger.timer = null;
if(tcpSlaveAgentListener!=null)
......
......@@ -20,6 +20,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
FilePath.did_not_manage_to_validate_may_be_too_sl=Did not manage to validate {0} (may be too slow)
FilePath.validateAntFileMask.whitespaceSeprator=\
Whitespace can no longer be used as the separator. Please Use '','' as the separator instead.
FilePath.validateAntFileMask.doesntMatchAndSuggest=\
......
......@@ -20,8 +20,11 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
SCMTrigger.DisplayName=Scruter l''outil de gestion de version
SCMTrigger.DisplayName=Scrutation de l''outil de gestion de version
SCMTrigger.getDisplayName=Log du dernier accès à {0}
SCMTrigger.SCMTriggerCause.ShortDescription=Un changement dans la base de code a provoqué le lancement de ce job
SCMTrigger.BuildAction.DisplayName=Log de scrutation
SCMTrigger.SCMTriggerCause.ShortDescription=Lancé par un changement dans la base de code
TimerTrigger.DisplayName=Construire périodiquement
TimerTrigger.TimerTriggerCause.ShortDescription=L''\u00e9\u00e9ch\u00e9ance d''une alarme p\u00e9riodique a provoqu\u00e9 le lancement de ce job
TimerTrigger.MissingWhitespace=Il semble manquer un espace entre * et *.
TimerTrigger.TimerTriggerCause.ShortDescription=Lancé par une alarme périodique
Trigger.init=Initialisation des minuteurs des déclencheurs
......@@ -24,6 +24,7 @@ ClockDifference.InSync=In sync
ClockDifference.Ahead=\ ahead
ClockDifference.Behind=\ behind
ClockDifference.Failed=Failed to check
FormFieldValidator.did_not_manage_to_validate_may_be_too_sl=Did not manage to validate {0} (may be too slow)
FormValidation.ValidateRequired=Required
FormValidation.Error.Details=(show details)
HttpResponses.Saved=Saved
\ No newline at end of file
HttpResponses.Saved=Saved
......@@ -424,4 +424,55 @@ public class FilePathTest extends ChannelTestCase {
assertEquals("/opt/jenkins/workspace/foo/bar/manchu", new FilePath(nixPath, "foo/bar\\manchu").getRemote());
assertEquals("/opt/jenkins/workspace/foo/bar/manchu", new FilePath(nixPath, "foo/bar/manchu").getRemote());
}
public void testValidateAntFileMask() throws Exception {
File tmp = Util.createTempDir();
try {
FilePath d = new FilePath(french, tmp.getPath());
d.child("d1/d2/d3").mkdirs();
d.child("d1/d2/d3/f.txt").touch(0);
d.child("d1/d2/d3/f.html").touch(0);
d.child("d1/d2/f.txt").touch(0);
assertValidateAntFileMask(null, d, "**/*.txt");
assertValidateAntFileMask(null, d, "d1/d2/d3/f.txt");
assertValidateAntFileMask(null, d, "**/*.html");
assertValidateAntFileMask(Messages.FilePath_validateAntFileMask_portionMatchButPreviousNotMatchAndSuggest("**/*.js", "**", "**/*.js"), d, "**/*.js");
assertValidateAntFileMask(Messages.FilePath_validateAntFileMask_doesntMatchAnything("index.htm"), d, "index.htm");
assertValidateAntFileMask(Messages.FilePath_validateAntFileMask_doesntMatchAndSuggest("f.html", "d1/d2/d3/f.html"), d, "f.html");
// XXX lots more to test, e.g. multiple patterns separated by commas; ought to have full code coverage for this method
} finally {
Util.deleteRecursive(tmp);
}
}
private static void assertValidateAntFileMask(String expected, FilePath d, String fileMasks) throws Exception {
assertEquals(expected, d.validateAntFileMask(fileMasks));
}
@Bug(7214)
public void testValidateAntFileMaskBounded() throws Exception {
File tmp = Util.createTempDir();
try {
FilePath d = new FilePath(french, tmp.getPath());
FilePath d2 = d.child("d1/d2");
d2.mkdirs();
for (int i = 0; i < 100; i++) {
FilePath d3 = d2.child("d" + i);
d3.mkdirs();
d3.child("f.txt").touch(0);
}
assertEquals(null, d.validateAntFileMask("d1/d2/**/f.txt"));
assertEquals(null, d.validateAntFileMask("d1/d2/**/f.txt", 10));
assertEquals(Messages.FilePath_validateAntFileMask_portionMatchButPreviousNotMatchAndSuggest("**/*.js", "**", "**/*.js"), d.validateAntFileMask("**/*.js", 1000));
try {
d.validateAntFileMask("**/*.js", 10);
fail();
} catch (InterruptedException x) {
// good
}
} finally {
Util.deleteRecursive(tmp);
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册