/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.apache.brooklyn.entity.group.zoneaware; import java.util.Iterator; import java.util.List; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.location.Location; import org.apache.brooklyn.entity.group.DynamicCluster.ZoneFailureDetector; import com.google.common.annotations.Beta; import com.google.common.base.Ticker; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @Beta public abstract class AbstractZoneFailureDetector implements ZoneFailureDetector { private final ConcurrentMap<Location, ZoneHistory> zoneHistories = Maps.newConcurrentMap(); protected final Ticker ticker; public AbstractZoneFailureDetector() { this(Ticker.systemTicker()); } public AbstractZoneFailureDetector(Ticker ticker) { this.ticker = ticker; } @Override public void onStartupSuccess(Location loc, Entity entity) { getZoneHistory(loc).onSuccess(currentTimeMillis()); } @Override public void onStartupFailure(Location loc, Entity entity, Throwable cause) { getZoneHistory(loc).onFailure(currentTimeMillis(), cause); } @Override public boolean hasFailed(Location loc) { ZoneHistory zoneHistory = getZoneHistory(loc); return doHasFailed(loc, zoneHistory); } protected long currentTimeMillis() { return TimeUnit.NANOSECONDS.toMillis(ticker.read()); } protected ZoneHistory getZoneHistory(Location loc) { ZoneHistory zoneHistory = zoneHistories.get(loc); if (zoneHistory == null) { ZoneHistory newZoneHistory = newZoneHistory(loc); ZoneHistory oldZoneHistory = zoneHistories.putIfAbsent(loc, newZoneHistory); zoneHistory = (oldZoneHistory != null) ? oldZoneHistory : newZoneHistory; } return zoneHistory; } protected ZoneHistory newZoneHistory(Location loc) { return new ZoneHistory(); } /** * Warn: called should normally synchronize on zoneHistory while accessing it. */ protected abstract boolean doHasFailed(Location loc, ZoneHistory zoneHistory); /** * Note: callers please don't side-effect the success/failures/causes fields directly! * Instead consider sub-classing ZoneHistory, and overriding {@link AbstractZoneFailureDetector#newZoneHistory(Location)}. */ public static class ZoneHistory { public final List<Long> successes = Lists.newLinkedList(); public final List<Long> failures = Lists.newLinkedList(); public final List<Throwable> causes = Lists.newLinkedList(); public synchronized void onSuccess(long date) { successes.add(date); } public synchronized void onFailure(long date, Throwable cause) { failures.add(date); causes.add(cause); } public synchronized void trimOlderThan(long date) { assert failures.size() == causes.size() : failures.size()+" failures, but "+causes.size()+" causes; bad synchronization by callers"; for (Iterator<Long> iter = successes.iterator(); iter.hasNext();) { Long d = iter.next(); if (d < date) iter.remove(); } for (Iterator<Long> iter = failures.iterator(); iter.hasNext();) { Iterator<Throwable> causeIter = causes.iterator(); Long d = iter.next(); causeIter.next(); if (d < date) { iter.remove(); causeIter.remove(); } else { break; } } } } }