001: /**
002: * Sequoia: Database clustering technology.
003: * Copyright (C) 2006 Continuent, Inc.
004: * Contact: sequoia@continuent.org
005: *
006: * Licensed under the Apache License, Version 2.0 (the "License");
007: * you may not use this file except in compliance with the License.
008: * You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS,
014: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: * See the License for the specific language governing permissions and
016: * limitations under the License.
017: *
018: * Initial developer(s): Gilles Rayrat.
019: * Contributor(s): ______________________.
020: */package org.continuent.sequoia.driver;
021:
022: import java.util.HashMap;
023: import java.util.HashSet;
024: import java.util.Iterator;
025:
026: /**
027: * Provides a list of controllers and maintains their state (up or down),
028: * launching appropriatly methods from callback given callback instance.<br>
029: * To each controller is associated a lastTimeSeen value corresponding to the
030: * last time the controller responded to a ping. Their state is updated each
031: * time the lastTimeSeen value is accessed (read or written). At init time, all
032: * controllers are considered as up (responding to pings). Controllers can also
033: * be forced as 'no more responding to pings' (ie. down)
034: *
035: * @author <a href="mailto:gilles.rayrat@continuent.com">Gilles Rayrat</a>
036: * @version 1.0
037: */
038: public class WatchedControllers {
039: /**
040: * Holds a controller list index, lastTimeSeen variable and state (up or down)
041: */
042: private class ControllerIndexAndState {
043: /** This is the index of the controller in the original list */
044: int index;
045: /** Consider that a controller is up at init time */
046: boolean respondsToPings = true;
047: /**
048: * Init to time the object is constructed and updated at each ping received
049: */
050: long lastTimeSeen;
051:
052: ControllerIndexAndState(int idx, long initTime) {
053: index = idx;
054: lastTimeSeen = initTime;
055: }
056:
057: public void updateTime(long newTime) {
058: lastTimeSeen = newTime;
059: }
060:
061: public int getIndex() {
062: return index;
063: }
064:
065: public void setIndex(int idx) {
066: index = idx;
067: }
068:
069: public boolean isUp() {
070: return respondsToPings;
071: }
072:
073: public long getLastTimeSeen() {
074: return lastTimeSeen;
075: }
076:
077: public void setUp() {
078: respondsToPings = true;
079: }
080:
081: public void setDown() {
082: respondsToPings = false;
083: }
084: }
085:
086: /** All controllers with their corresponding state */
087: // This list is, for now, static. In the future, it could support dynamic
088: // insertions and suppressions
089: // The list will be heavily accessed for searches given a ControllerInfo
090: // (see setControllerResponsed), that's why we use a hash map
091: final HashMap allControllersAndStates;
092:
093: /**
094: * Delay after which a controller will be considered as failing if it did not
095: * respond to pings
096: */
097: int controllerTimeout;
098: /** Actions to take when a controller goes down or back-alive */
099: ControllerStateChangedCallback callback;
100:
101: /**
102: * Creates a new list of watched controllers with the given list of
103: * controllers and an initial lastTimeSeen value.<br>
104: *
105: * @param controllers list of all controllers identified by their
106: * <code>ControllerInfo</code>
107: * @param initTime initial lastTimeSeen value, typically
108: * <code>System.currentTimeMillis()</code>
109: * @param callback Callback implementation to call when a controller state
110: * changes
111: * @param controllerTimeout delay after which a controller will be considered
112: * as failing if it did not respond to pings
113: */
114: public WatchedControllers(ControllerInfo[] controllers,
115: long initTime, int controllerTimeout,
116: ControllerStateChangedCallback callback) {
117: int numberOfControllers = 0; // if caller wants to add controllers later
118: if (controllers != null)
119: numberOfControllers = controllers.length;
120: allControllersAndStates = new HashMap(numberOfControllers);
121: // we are inside a contructor, so we can safely operate on the
122: // list without worrying of concurrent modifications
123: for (int i = 0; i < numberOfControllers; i++) {
124: // controllers can't be null here, due to for loop clause
125: this .allControllersAndStates.put(controllers[i],
126: new ControllerIndexAndState(i, initTime)); // keep the original index
127: }
128: this .controllerTimeout = controllerTimeout;
129: this .callback = callback;
130: }
131:
132: /**
133: * Associates given lastTimeSeen value to specified controller and update its
134: * state: if the controller was considered as down, calls the
135: * {@link ControllerStateChangedCallback#onControllerUp(ControllerInfo)}
136: *
137: * @see ControllerStateChangedCallback
138: */
139: public void setControllerResponsed(ControllerInfo ctrl, long newTime) {
140: synchronized (allControllersAndStates) {
141: ControllerIndexAndState state = (ControllerIndexAndState) allControllersAndStates
142: .get(ctrl);
143: if (state == null) {
144: // Well, this should never happen, this is a bug
145: System.err.println("ERROR: Unknown controller " + ctrl
146: + " responded to ping! (list="
147: + allControllersAndStates + ")");
148: return;
149: }
150: // if the controller was down, we launch the callback
151: if (!state.isUp()) {
152: state.setUp();
153: callback.onControllerUp(ctrl);
154: }
155: // Set the new time
156: state.updateTime(newTime);
157: }
158: }
159:
160: /**
161: * Updates all controllers state according to the given time.<br>
162: * Iterates through the controller list and (if the controller was not already
163: * considered as down) if the lastTimeSeen value is older than the given time
164: * minus {@link #controllerTimeout}, the controller will the be considered as
165: * failing, and
166: * {@link ControllerStateChangedCallback#onControllerDown(ControllerInfo)}
167: * will be called.
168: */
169: public void lookForDeadControllers(long currentTime) {
170: for (Iterator iter = getControllerIterator(); iter.hasNext();) {
171: ControllerInfo ctrl = (ControllerInfo) iter.next();
172: ControllerIndexAndState state = (ControllerIndexAndState) allControllersAndStates
173: .get(ctrl);
174: if (state.isUp()
175: && currentTime - state.getLastTimeSeen() > controllerTimeout) {
176: state.setDown();
177: callback.onControllerDown(ctrl);
178: }
179: }
180: }
181:
182: /**
183: * Forces the given controller to be considered as down.<br>
184: * If the given controller was already down, does nothing. Otherwise, marks it
185: * as dead and calls
186: * {@link ControllerStateChangedCallback#onControllerDown(ControllerInfo)}
187: */
188: public synchronized void setControllerDown(ControllerInfo ctrl) {
189: ControllerIndexAndState state = (ControllerIndexAndState) allControllersAndStates
190: .get(ctrl);
191: if (state.isUp()) // don't do the job twice
192: {
193: state.setDown();
194: callback.onControllerDown(ctrl);
195: }
196: }
197:
198: /**
199: * Returns a 'safe' read-only iterator on controllers' ControllerInfo.<br>
200: * Creates a copy of the controller hashmap keys and returns a iterator on it.
201: * This way, the iterator will not be affected by hashmap operations
202: */
203: public final Iterator getControllerIterator() {
204: HashMap copy = getControllersClone();
205: return (new HashSet(copy.keySet())).iterator();
206: }
207:
208: /**
209: * Creates and returns a copy of the controller hashmap member variable for
210: * thread-safe read operations purpose.
211: */
212: private HashMap getControllersClone() {
213: HashMap copy = null;
214: synchronized (allControllersAndStates) {
215: copy = (HashMap) (allControllersAndStates).clone();
216: }
217: return copy;
218: }
219:
220: /**
221: * Returns the given controllers index in the original list
222: *
223: * @param controller the controller to get index of
224: * @return original index of the given controller
225: */
226: public int getOriginalIndexOf(ControllerInfo controller) {
227: if (!allControllersAndStates.containsKey(controller)) {
228: // this is a bug => return valid index anyway
229: System.err.println("ERROR: Unknown controller "
230: + controller + " to get index of! (list="
231: + allControllersAndStates + ")");
232: return 0;
233: }
234: return ((ControllerIndexAndState) allControllersAndStates
235: .get(controller)).index;
236: }
237:
238: /**
239: * <b>NOT IMPLEMENTED YET</b><br>
240: * Adds a controller to the list and associates the given lastTimeSeenValue to
241: * it.<br>
242: * Note that this function is not used for now, but is present for future
243: * dynamic controller addition<br>
244: * This operation is thread-safe, thus can slow-down other concurrent
245: * operations like {@link #removeController(ControllerInfo)},
246: * {@link #setControllerResponsed(ControllerInfo, long)},
247: */
248: public void addController(ControllerInfo controller, int index,
249: long lastTimeSeen) {
250: synchronized (allControllersAndStates) {
251: // TODO: we have to move the indexes of all controllers with index greater
252: // or equal to the given index
253: // ie. iterate through the list, if (ctrlIdx >= index) ctrlIdx++
254: // then we can add the controller like this:
255: // allControllersAndStates.put(controller, new ControllerIndexAndState(
256: // index, lastTimeSeen));
257: throw new RuntimeException(
258: "Dynamic addition of controllers not implemented yet");
259: }
260: }
261:
262: /**
263: * <b>NOT IMPLEMENTED YET</b><br>
264: * Removes the selected controller from the list of controllers to watch
265: *
266: * @param controller controller to remove
267: * @see #addController(ControllerInfo, long)
268: */
269: public void removeController(ControllerInfo controller) {
270: synchronized (allControllersAndStates) {
271: // TODO: we have to move the indexes of all controllers with index greater
272: // or equal to the given index
273: // ie. iterate through the list, if (ctrlIdx >= index) ctrlIdx--
274: // then we can add the controller like this:
275: // allControllersAndStates.remove(controller);
276: throw new RuntimeException(
277: "Dynamic suppression of controllers not implemented yet");
278: }
279: }
280:
281: public String toString() {
282: StringBuffer result = new StringBuffer("{");
283:
284: Iterator ctrls = getControllerIterator();
285: if (ctrls.hasNext())
286: result.append(ctrls.next());
287:
288: while (ctrls.hasNext())
289: result.append(", " + ctrls.next());
290:
291: result.append("}");
292:
293: return result.toString();
294: }
295: }
|