/* * The MIT License * * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package hudson.model; import jenkins.util.SystemProperties; import hudson.model.Descriptor.FormException; import java.io.IOException; import java.util.LinkedHashSet; import java.util.Set; import java.util.SortedMap; import java.util.logging.Level; import java.util.logging.Logger; import javax.servlet.ServletException; import jenkins.model.Jenkins; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; /** * {@link Job} that monitors activities that happen outside Hudson, * which requires occasional batch reload activity to obtain the up-to-date information. * * <p> * This can be used as a base class to derive custom {@link Job} type. * * @author Kohsuke Kawaguchi */ public abstract class ViewJob<JobT extends ViewJob<JobT,RunT>, RunT extends Run<JobT,RunT>> extends Job<JobT,RunT> { private static final Logger LOGGER = Logger.getLogger(ViewJob.class.getName()); /** * We occasionally update the list of {@link Run}s from a file system. * The next scheduled update time. */ private transient long nextUpdate = 0; /** * All {@link Run}s. Copy-on-write semantics. */ protected transient /*almost final*/ RunMap<RunT> runs = new RunMap<RunT>(); private transient boolean notLoaded = true; /** * If the reloading of runs are in progress (in another thread, * set to true.) */ private transient volatile boolean reloadingInProgress; private static ReloadThread reloadThread; static synchronized void interruptReloadThread() { if (reloadThread != null) { reloadThread.interrupt(); } } /** * @deprecated as of 1.390 */ @Deprecated protected ViewJob(Jenkins parent, String name) { super(parent,name); } protected ViewJob(ItemGroup parent, String name) { super(parent,name); } public boolean isBuildable() { return false; } @Override public void onLoad(ItemGroup<? extends Item> parent, String name) throws IOException { super.onLoad(parent, name); notLoaded = true; } protected SortedMap<Integer,RunT> _getRuns() { if(notLoaded || runs==null) { // if none is loaded yet, do so immediately. synchronized(this) { if(runs==null) runs = new RunMap<RunT>(); if(notLoaded) { notLoaded = false; _reload(); } } } if(nextUpdate<System.currentTimeMillis()) { if(!reloadingInProgress) { // schedule a new reloading operation. // we don't want to block the current thread, // so reloading is done asynchronously. reloadingInProgress = true; Set<ViewJob> reloadQueue; synchronized (ViewJob.class) { if (reloadThread == null) { reloadThread = new ReloadThread(); reloadThread.start(); } reloadQueue = reloadThread.reloadQueue; } synchronized(reloadQueue) { reloadQueue.add(this); reloadQueue.notify(); } } } return runs; } public void removeRun(RunT run) { if (runs != null && !runs.remove(run)) { LOGGER.log(Level.WARNING, "{0} did not contain {1} to begin with", new Object[] {this, run}); } } private void _reload() { try { reload(); } finally { reloadingInProgress = false; nextUpdate = reloadPeriodically ? System.currentTimeMillis()+1000*60 : Long.MAX_VALUE; } } /** * Reloads the list of {@link Run}s. This operation can take a long time. * * <p> * The loaded {@link Run}s should be set to {@link #runs}. */ protected abstract void reload(); @Override protected void submit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, FormException { super.submit(req,rsp); // make sure to reload to reflect this config change. nextUpdate = 0; } /** * Thread that reloads the {@link Run}s. */ private static final class ReloadThread extends Thread { /** * {@link ExternalJob}s that need to be reloaded. * * This is a set, so no {@link ExternalJob}s are scheduled twice, yet * it's order is predictable, avoiding starvation. */ final Set<ViewJob> reloadQueue = new LinkedHashSet<ViewJob>(); private ReloadThread() { setName("ViewJob reload thread"); } private ViewJob getNext() throws InterruptedException { synchronized(reloadQueue) { // reload operations might eat InterruptException, // so check the status every so often while(reloadQueue.isEmpty() && !terminating()) reloadQueue.wait(60*1000); if(terminating()) throw new InterruptedException(); // terminate now ViewJob job = reloadQueue.iterator().next(); reloadQueue.remove(job); return job; } } private boolean terminating() { return Jenkins.getInstance().isTerminating(); } @Override public void run() { while (!terminating()) { try { getNext()._reload(); } catch (InterruptedException e) { // treat this as a death signal return; } catch (Throwable t) { // otherwise ignore any error t.printStackTrace(); } } } } // private static final Logger logger = Logger.getLogger(ViewJob.class.getName()); /** * In the very old version of Hudson, an external job submission was just creating files on the file system, * so we needed to periodically reload the jobs from a file system to pick up new records. * * <p> * We then switched to submission via HTTP, so this reloading is no longer necessary, so only do this * when explicitly requested. * */ public static boolean reloadPeriodically = SystemProperties.getBoolean(ViewJob.class.getName()+".reloadPeriodically"); }