/* dCache - http://www.dcache.org/ * * Copyright (C) 2015 Deutsches Elektronen-Synchrotron * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.dcache.srm.scheduler.strategy; import com.google.common.collect.ConcurrentHashMultiset; import com.google.common.collect.Multiset; import org.dcache.srm.request.Job; import org.dcache.srm.scheduler.Scheduler; import org.dcache.srm.scheduler.State; import org.dcache.srm.scheduler.StateChangeListener; import org.dcache.srm.scheduler.spi.TransferStrategy; /** * Provides a fair share transfer strategy. * * Allows TURLs to be handed out according to a fair share of the ready slots. A client is entitled to * its share by having requests in READY or RQUEUED. This strategy may leave transfer slots unused as * a result of one client having requests in RQUEUED without readying them. In other words, if a client * has requests in RQUEUED, this strategy will try to keep the client's fair share available. */ public class FairShareTransferStrategy extends ForwardingJobDiscriminator implements TransferStrategy, StateChangeListener { private final Multiset<String> rqueued = ConcurrentHashMultiset.create(); private final Multiset<String> ready = ConcurrentHashMultiset.create(); private final Scheduler scheduler; public FairShareTransferStrategy(Scheduler scheduler, String discriminator) { super(discriminator); this.scheduler = scheduler; scheduler.addStateChangeListener(this); } @Override public void stateChanged(Job job, State oldState, State newState) { if (oldState == State.RQUEUED && newState != State.RQUEUED) { rqueued.remove(getDiscriminatingValue(job)); } else if (oldState != State.RQUEUED && newState == State.RQUEUED) { rqueued.add(getDiscriminatingValue(job)); } if (oldState == State.READY && newState != State.READY) { ready.remove(getDiscriminatingValue(job)); } else if (oldState != State.READY && newState == State.READY) { ready.add(getDiscriminatingValue(job)); } } @Override public boolean canTransfer(Job job) { int readySize = ready.size(); int queuedSize = rqueued.size(); int max = scheduler.getMaxReadyJobs(); /* Common cases that are cheap to check for. */ if (readySize + queuedSize <= max) { return true; } if (readySize >= max) { return false; } /* The following isn't accurate in the presence of concurrent state change * notifications, but we trade an approximate result for lock contention. */ /* We count how many other requests would need to be readied before job would * be allowed to start. If the current number of ready requests plus the number * of requests ahead of job is below the max, then we allow job to proceed. */ int count = ready.count(getDiscriminatingValue(job)); int aheadOfJob = ready.entrySet().stream() .mapToInt(e -> Math.min(rqueued.count(e.getElement()), Math.max(0, count - e.getCount()))) .sum(); return readySize + aheadOfJob < max; } }