View Javadoc

1   /***
2    *  Copyright 2003-2010 Terracotta, Inc.
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  
17  package net.sf.ehcache.terracotta;
18  
19  import net.sf.ehcache.CacheException;
20  import net.sf.ehcache.Disposable;
21  import net.sf.ehcache.Ehcache;
22  import net.sf.ehcache.Status;
23  import net.sf.ehcache.distribution.RemoteCacheException;
24  import net.sf.ehcache.store.MemoryLimitedCacheLoader;
25  import org.slf4j.Logger;
26  import org.slf4j.LoggerFactory;
27  
28  import java.io.IOException;
29  import java.util.Set;
30  
31  /***
32   * A {@link net.sf.ehcache.bootstrap.BootstrapCacheLoader} that will load Elements into a Terracotta clustered cache, based on a previously
33   * snapshotted key set. It is also responsible to create snapshot files to disk
34   * @author Alex Snaps
35   */
36  public class TerracottaBootstrapCacheLoader extends MemoryLimitedCacheLoader implements Disposable {
37  
38      /***
39       * The default interval in seconds, between each snapshot
40       */
41      public static final long DEFAULT_INTERVAL = 10 * 60;
42      /***
43       * The default on whether to do the snapshot on a dedicated thread or using the CacheManager's
44       * {@link java.util.concurrent.ScheduledExecutorService}
45       */
46      public static final boolean DEFAULT_DEDICATED_THREAD = false;
47  
48      private static final Logger LOG = LoggerFactory.getLogger(TerracottaBootstrapCacheLoader.class);
49  
50      private final boolean aSynchronous;
51      private final boolean doKeySnapshot;
52      private final boolean doKeySnapshotOnDedicatedThread;
53      private final long interval;
54      private final String directory;
55  
56      private volatile KeySnapshotter keySnapshotter;
57      private volatile boolean immediateShutdown;
58      private volatile boolean doKeySnapshotOnDispose;
59  
60      private TerracottaBootstrapCacheLoader(final boolean doKeySnapshot, final boolean aSynchronous,
61                                             final String directory, final long interval, final boolean doKeySnapshotOnDedicatedThread) {
62          this.aSynchronous = aSynchronous;
63          this.doKeySnapshot = doKeySnapshot;
64          this.doKeySnapshotOnDedicatedThread = doKeySnapshotOnDedicatedThread;
65          this.interval = interval;
66          this.directory = directory;
67      }
68  
69      /***
70       * Constructor
71       * @param asynchronous do the loading asynchronously, or synchronously
72       * @param directory the directory to read snapshot files from, and write them to
73       * @param doKeySnapshots Whether to do keysnapshotting
74       */
75      public TerracottaBootstrapCacheLoader(final boolean asynchronous, String directory, boolean doKeySnapshots) {
76          this(doKeySnapshots, asynchronous, directory, DEFAULT_INTERVAL, DEFAULT_DEDICATED_THREAD);
77      }
78  
79      /***
80       * Constructor
81       * @param asynchronous do the loading asynchronously, or synchronously
82       * @param directory the directory to read snapshot files from, and write them to
83       * @param interval the interval in seconds at which the snapshots of the local key set has to occur
84       */
85      public TerracottaBootstrapCacheLoader(final boolean asynchronous, String directory, long interval) {
86          this(asynchronous, directory, interval, false);
87      }
88  
89      /***
90       * Constructor
91       * @param asynchronous do the loading asynchronously, or synchronously
92       * @param directory the directory to read snapshot files from, and write them to
93       * @param interval the interval in seconds at which the snapshots of the local key set has to occur
94       * @param onDedicatedThread whether to do the snapshot on a dedicated thread or using the CacheManager's
95       * {@link java.util.concurrent.ScheduledExecutorService ScheduledExecutorService}
96       */
97      public TerracottaBootstrapCacheLoader(final boolean asynchronous, String directory, long interval, boolean onDedicatedThread) {
98          this(true, asynchronous, directory, interval, onDedicatedThread);
99      }
100 
101     /***
102      * Whether the on going keysnapshot will finish before the instance is disposed
103      * @return true if disposable is immediate
104      * @see Disposable
105      */
106     public boolean isImmediateShutdown() {
107         return immediateShutdown;
108     }
109 
110     /***
111      * Sets whether the disposal of the instance will let the potential current key set being written to disk finish, or whether the
112      * shutdown will be immediate
113      * @param immediateShutdown true if immediate, false to let the snapshot finish
114      */
115     public void setImmediateShutdown(final boolean immediateShutdown) {
116         this.immediateShutdown = immediateShutdown;
117     }
118 
119     /***
120      * {@inheritDoc}
121      */
122     public void load(final Ehcache cache) throws CacheException {
123         if (!cache.getCacheConfiguration().isTerracottaClustered()) {
124             LOG.error("You're trying to bootstrap a non Terracotta clustered cache with a TerracottaBootstrapCacheLoader! Cache " +
125                       "'{}' will not be bootstrapped and no keySet snapshot will be recorded...", cache.getName());
126             return;
127         }
128 
129         if (cache.getStatus() != Status.STATUS_ALIVE) {
130             throw new CacheException("Cache '" + cache.getName() + "' isn't alive yet: " + cache.getStatus());
131         }
132 
133         if (isAsynchronous()) {
134             BootstrapThread thread = new BootstrapThread(cache);
135             thread.start();
136         } else {
137             doLoad(cache);
138         }
139     }
140 
141     private void doLoad(final Ehcache cache) {
142         final RotatingSnapshotFile snapshotFile = new RotatingSnapshotFile(directory == null ? cache.getCacheManager()
143             .getDiskStorePath() : directory, cache.getName());
144         try {
145             final Set<Object> keys = snapshotFile.readAll();
146             int loaded = 0;
147             for (Object key : keys) {
148                 if (isInMemoryLimitReached(cache, loaded)) {
149                     break;
150                 }
151                 cache.get(key);
152                 loaded++;
153             }
154             LOG.info("Finished loading {} keys (of {} on disk) from previous snapshot for Cache '{}'",
155                 new Object[] {Integer.valueOf(loaded), keys.size(), cache.getName()});
156         } catch (IOException e) {
157             LOG.error("Couldn't load keySet for Cache '{}'", cache.getName(), e);
158         }
159 
160         if (doKeySnapshot) {
161             keySnapshotter = new KeySnapshotter(cache, interval, doKeySnapshotOnDedicatedThread, snapshotFile);
162         }
163     }
164 
165     /***
166      * {@inheritDoc}
167      */
168     public boolean isAsynchronous() {
169         return aSynchronous;
170     }
171 
172     /***
173      * Will shut the keysnapshot thread and other resources down.
174      * If a snapshot is currently in progress, the method will either shutdown immediately or let the snapshot finish
175      * based on the configured {@link #setImmediateShutdown(boolean)} value
176      */
177     public void dispose() {
178         if (keySnapshotter != null) {
179             if (doKeySnapshotOnDispose) {
180                 try {
181                     keySnapshotter.doSnapshot();
182                 } catch (IOException e) {
183                     LOG.error("Error writing local key set for Cache '{}'", keySnapshotter.getCacheName(), e);
184                 }
185             } else {
186                 keySnapshotter.dispose(immediateShutdown);
187             }
188         }
189     }
190 
191     /***
192      * Calling this method will result in a snapshot being taken or wait for the one in progress to finish
193      * @throws IOException On exception being thrown while doing the snapshot
194      */
195     public void doLocalKeySnapshot() throws IOException {
196         keySnapshotter.doSnapshot();
197     }
198 
199     /***
200      * {@inheritDoc}
201      */
202     @Override
203     public Object clone() throws CloneNotSupportedException {
204         return super.clone();
205     }
206 
207     /***
208      * Accessor to the associated {@link KeySnapshotter}
209      * @return the {@link KeySnapshotter} used by this loader instance
210      */
211     KeySnapshotter getKeySnapshotter() {
212         return keySnapshotter;
213     }
214 
215     /***
216      * Configures the Loader to take a snapshot when it is being disposed
217      * @param doKeySnapshotOnDispose whether to snapshot on loader disposal
218      */
219     public void setSnapshotOnDispose(final boolean doKeySnapshotOnDispose) {
220         this.doKeySnapshotOnDispose = doKeySnapshotOnDispose;
221     }
222 
223     /***
224      * A background daemon thread that asynchronously calls doLoad
225      */
226     private final class BootstrapThread extends Thread {
227         private Ehcache cache;
228 
229         public BootstrapThread(Ehcache cache) {
230             super("Bootstrap Thread for cache " + cache.getName());
231             this.cache = cache;
232             setDaemon(true);
233             setPriority(Thread.NORM_PRIORITY);
234         }
235 
236         /***
237          * RemoteDebugger thread method.
238          */
239         public final void run() {
240             try {
241                 doLoad(cache);
242             } catch (RemoteCacheException e) {
243                 LOG.warn("Error asynchronously performing bootstrap. The cause was: " + e.getMessage(), e);
244             }
245             cache = null;
246         }
247     }
248 }