1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.hadoop.hbase.master.cleaner;
19
20 import java.io.IOException;
21 import java.util.Collections;
22 import java.util.LinkedList;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.concurrent.ExecutionException;
26 import java.util.concurrent.ForkJoinPool;
27 import java.util.concurrent.ForkJoinTask;
28 import java.util.concurrent.RecursiveTask;
29 import java.util.concurrent.atomic.AtomicBoolean;
30
31 import org.apache.commons.logging.Log;
32 import org.apache.commons.logging.LogFactory;
33 import org.apache.hadoop.conf.Configuration;
34 import org.apache.hadoop.fs.FileStatus;
35 import org.apache.hadoop.fs.FileSystem;
36 import org.apache.hadoop.fs.Path;
37 import org.apache.hadoop.fs.PathIsNotEmptyDirectoryException;
38 import org.apache.hadoop.hbase.ScheduledChore;
39 import org.apache.hadoop.hbase.Stoppable;
40 import org.apache.hadoop.hbase.classification.InterfaceAudience;
41 import org.apache.hadoop.hbase.conf.ConfigurationObserver;
42 import org.apache.hadoop.hbase.util.FSUtils;
43 import org.apache.hadoop.hbase.util.FileStatusFilter;
44 import org.apache.hadoop.ipc.RemoteException;
45
46 import com.google.common.annotations.VisibleForTesting;
47 import com.google.common.base.Preconditions;
48 import com.google.common.collect.ImmutableSet;
49 import com.google.common.collect.Iterables;
50 import com.google.common.collect.Lists;
51
52
53
54
55
56 @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD",
57 justification="Static pool will be only updated once.")
58 @InterfaceAudience.Private
59 public abstract class CleanerChore<T extends FileCleanerDelegate> extends ScheduledChore
60 implements ConfigurationObserver {
61
62 private static final Log LOG = LogFactory.getLog(CleanerChore.class.getName());
63 private static final int AVAIL_PROCESSORS = Runtime.getRuntime().availableProcessors();
64
65
66
67
68
69
70
71 public static final String CHORE_POOL_SIZE = "hbase.cleaner.scan.dir.concurrent.size";
72 private static final String DEFAULT_CHORE_POOL_SIZE = "0.25";
73
74 private static class DirScanPool {
75 int size;
76 ForkJoinPool pool;
77 int cleanerLatch;
78 AtomicBoolean reconfigNotification;
79
80 DirScanPool(Configuration conf) {
81 String poolSize = conf.get(CHORE_POOL_SIZE, DEFAULT_CHORE_POOL_SIZE);
82 size = calculatePoolSize(poolSize);
83
84
85 size = size == 0 ? calculatePoolSize(DEFAULT_CHORE_POOL_SIZE) : size;
86 pool = new ForkJoinPool(size);
87 LOG.info("Cleaner pool size is " + size);
88 reconfigNotification = new AtomicBoolean(false);
89 cleanerLatch = 0;
90 }
91
92
93
94
95
96 synchronized void markUpdate(Configuration conf) {
97 int newSize = calculatePoolSize(conf.get(CHORE_POOL_SIZE, DEFAULT_CHORE_POOL_SIZE));
98 if (newSize == size) {
99 LOG.trace("Size from configuration is same as previous=" + newSize +
100 " no need to update.");
101 return;
102 }
103 size = newSize;
104
105 reconfigNotification.set(true);
106 }
107
108
109
110
111 synchronized void updatePool(long timeout) {
112 long stopWaitTime = System.currentTimeMillis() + timeout;
113 while (cleanerLatch != 0 && timeout > 0) {
114 try {
115 wait(timeout);
116 timeout = stopWaitTime - System.currentTimeMillis();
117 } catch (InterruptedException ie) {
118 Thread.currentThread().interrupt();
119 break;
120 }
121 }
122 shutDownNow();
123 LOG.info("Update chore's pool size from " + pool.getParallelism() + " to " + size);
124 pool = new ForkJoinPool(size);
125 }
126
127 synchronized void latchCountUp() {
128 cleanerLatch++;
129 }
130
131 synchronized void latchCountDown() {
132 cleanerLatch--;
133 notifyAll();
134 }
135
136 @SuppressWarnings({"FutureReturnValueIgnored","rawtypes","unchecked"})
137 synchronized void submit(ForkJoinTask task) {
138 pool.submit(task);
139 }
140
141 synchronized void shutDownNow() {
142 if (pool == null || pool.isShutdown()) {
143 return;
144 }
145 pool.shutdownNow();
146 }
147 }
148
149
150 private static volatile DirScanPool POOL;
151
152 protected final FileSystem fs;
153 private final Path oldFileDir;
154 private final Configuration conf;
155 protected List<T> cleanersChain;
156 protected Map<String, Object> params;
157 private AtomicBoolean enabled = new AtomicBoolean(true);
158
159 public static void initChorePool(Configuration conf) {
160 if (POOL == null) {
161 POOL = new DirScanPool(conf);
162 }
163 }
164
165 public static void shutDownChorePool() {
166 if (POOL != null) {
167 POOL.shutDownNow();
168 POOL = null;
169 }
170 }
171
172 public CleanerChore(String name, final int sleepPeriod, final Stoppable s, Configuration conf,
173 FileSystem fs, Path oldFileDir, String confKey) {
174 this(name, sleepPeriod, s, conf, fs, oldFileDir, confKey, null);
175 }
176
177
178
179
180
181
182
183
184
185
186
187 public CleanerChore(String name, final int sleepPeriod, final Stoppable s, Configuration conf,
188 FileSystem fs, Path oldFileDir, String confKey, Map<String, Object> params) {
189 super(name, s, sleepPeriod);
190
191 Preconditions.checkNotNull(POOL, "Chore's pool isn't initialized, please call"
192 + "CleanerChore.initChorePool(Configuration) before new a cleaner chore.");
193 this.fs = fs;
194 this.oldFileDir = oldFileDir;
195 this.conf = conf;
196 this.params = params;
197 initCleanerChain(confKey);
198 }
199
200
201
202
203
204
205
206 static int calculatePoolSize(String poolSize) {
207 if (poolSize.matches("[1-9][0-9]*")) {
208
209
210 int size = Math.min(Integer.parseInt(poolSize), AVAIL_PROCESSORS);
211 if (size == AVAIL_PROCESSORS) {
212 LOG.warn("Use full core processors to scan dir, size=" + size);
213 }
214 return size;
215 } else if (poolSize.matches("0.[0-9]+|1.0")) {
216
217
218 int computedThreads = (int) (AVAIL_PROCESSORS * Double.valueOf(poolSize));
219 if (computedThreads < 1) {
220 LOG.debug("Computed " + computedThreads + " threads for CleanerChore, using 1 instead");
221 return 1;
222 }
223 return computedThreads;
224 } else {
225 LOG.error("Unrecognized value: " + poolSize + " for " + CHORE_POOL_SIZE +
226 ", use default config: " + DEFAULT_CHORE_POOL_SIZE + " instead.");
227 return calculatePoolSize(DEFAULT_CHORE_POOL_SIZE);
228 }
229 }
230
231
232
233
234
235
236
237 protected abstract boolean validate(Path file);
238
239
240
241
242
243 private void initCleanerChain(String confKey) {
244 this.cleanersChain = new LinkedList<T>();
245 String[] logCleaners = conf.getStrings(confKey);
246 if (logCleaners != null) {
247 for (String className : logCleaners) {
248 T logCleaner = newFileCleaner(className, conf);
249 if (logCleaner != null) {
250 LOG.debug("initialize cleaner=" + className);
251 this.cleanersChain.add(logCleaner);
252 }
253 }
254 }
255 }
256
257 @Override
258 public void onConfigurationChange(Configuration conf) {
259 POOL.markUpdate(conf);
260 }
261
262
263
264
265
266
267
268
269 private T newFileCleaner(String className, Configuration conf) {
270 try {
271 Class<? extends FileCleanerDelegate> c = Class.forName(className).asSubclass(
272 FileCleanerDelegate.class);
273 @SuppressWarnings("unchecked")
274 T cleaner = (T) c.newInstance();
275 cleaner.setConf(conf);
276 cleaner.init(this.params);
277 return cleaner;
278 } catch (Exception e) {
279 LOG.warn("Can NOT create CleanerDelegate: " + className, e);
280
281 return null;
282 }
283 }
284
285 @Override
286 protected void chore() {
287 if (getEnabled()) {
288 try {
289 POOL.latchCountUp();
290 if (runCleaner()) {
291 if (LOG.isTraceEnabled()) {
292 LOG.trace("Cleaned all WALs under " + oldFileDir);
293 }
294 } else {
295 if (LOG.isTraceEnabled()) {
296 LOG.trace("WALs outstanding under " + oldFileDir);
297 }
298 }
299 } finally {
300 POOL.latchCountDown();
301 }
302
303
304 if (POOL.reconfigNotification.compareAndSet(true, false)) {
305
306
307 POOL.updatePool((long) (0.8 * getTimeUnit().toMillis(getPeriod())));
308 }
309 } else {
310 LOG.trace("Cleaner chore disabled! Not cleaning.");
311 }
312 }
313
314 public Boolean runCleaner() {
315 CleanerTask task = new CleanerTask(this.oldFileDir, true);
316 POOL.submit(task);
317 return task.join();
318 }
319
320
321
322
323
324
325
326 private boolean checkAndDeleteFiles(List<FileStatus> files) {
327 if (files == null) {
328 return true;
329 }
330
331
332 List<FileStatus> validFiles = Lists.newArrayListWithCapacity(files.size());
333 List<FileStatus> invalidFiles = Lists.newArrayList();
334 for (FileStatus file : files) {
335 if (validate(file.getPath())) {
336 validFiles.add(file);
337 } else {
338 LOG.warn("Found a wrongly formatted file: " + file.getPath() + " - will delete it.");
339 invalidFiles.add(file);
340 }
341 }
342
343 Iterable<FileStatus> deletableValidFiles = validFiles;
344
345 for (T cleaner : cleanersChain) {
346 if (cleaner.isStopped() || getStopper().isStopped()) {
347 LOG.warn("A file cleaner" + this.getName() + " is stopped, won't delete any more files in:"
348 + this.oldFileDir);
349 return false;
350 }
351
352 Iterable<FileStatus> filteredFiles = cleaner.getDeletableFiles(deletableValidFiles);
353
354
355 if (LOG.isTraceEnabled()) {
356 ImmutableSet<FileStatus> filteredFileSet = ImmutableSet.copyOf(filteredFiles);
357 for (FileStatus file : deletableValidFiles) {
358 if (!filteredFileSet.contains(file)) {
359 LOG.trace(file.getPath() + " is not deletable according to:" + cleaner);
360 }
361 }
362 }
363
364 deletableValidFiles = filteredFiles;
365 }
366
367 Iterable<FileStatus> filesToDelete = Iterables.concat(invalidFiles, deletableValidFiles);
368 return deleteFiles(filesToDelete) == files.size();
369 }
370
371
372
373
374
375
376 protected int deleteFiles(Iterable<FileStatus> filesToDelete) {
377 int deletedFileCount = 0;
378 for (FileStatus file : filesToDelete) {
379 Path filePath = file.getPath();
380 LOG.trace("Removing " + file + " from archive");
381 try {
382 boolean success = this.fs.delete(filePath, false);
383 if (success) {
384 deletedFileCount++;
385 } else {
386 LOG.warn("Attempted to delete:" + filePath
387 + ", but couldn't. Run cleaner chain and attempt to delete on next pass.");
388 }
389 } catch (IOException e) {
390 e = e instanceof RemoteException ?
391 ((RemoteException)e).unwrapRemoteException() : e;
392 LOG.warn("Error while deleting: " + filePath, e);
393 }
394 }
395 return deletedFileCount;
396 }
397
398 @Override
399 public void cleanup() {
400 for (T lc : this.cleanersChain) {
401 try {
402 lc.stop("Exiting");
403 } catch (Throwable t) {
404 LOG.warn("Stopping", t);
405 }
406 }
407 }
408
409 @VisibleForTesting
410 int getChorePoolSize() {
411 return POOL.size;
412 }
413
414
415
416
417 public boolean setEnabled(final boolean enabled) {
418 return this.enabled.getAndSet(enabled);
419 }
420
421 public boolean getEnabled() {
422 return this.enabled.get();
423 }
424
425 private interface Action<T> {
426 T act() throws IOException;
427 }
428
429
430
431
432
433 private class CleanerTask extends RecursiveTask<Boolean> {
434 private final Path dir;
435 private final boolean root;
436
437 CleanerTask(final FileStatus dir, final boolean root) {
438 this(dir.getPath(), root);
439 }
440
441 CleanerTask(final Path dir, final boolean root) {
442 this.dir = dir;
443 this.root = root;
444 }
445
446 @Override
447 protected Boolean compute() {
448 LOG.trace("Cleaning under " + dir);
449 List<FileStatus> subDirs;
450 List<FileStatus> tmpFiles;
451 final List<FileStatus> files;
452 try {
453
454
455 subDirs = FSUtils.listStatusWithStatusFilter(fs, dir, new FileStatusFilter() {
456 @Override
457 public boolean accept(FileStatus f) {
458 return f.isDirectory();
459 }
460 });
461 if (subDirs == null) {
462 subDirs = Collections.emptyList();
463 }
464 tmpFiles = FSUtils.listStatusWithStatusFilter(fs, dir, new FileStatusFilter() {
465 @Override
466 public boolean accept(FileStatus f) {
467 return f.isFile();
468 }
469 });
470 files = tmpFiles == null ? Collections.<FileStatus>emptyList() : tmpFiles;
471 } catch (IOException ioe) {
472 LOG.warn("failed to get FileStatus for contents of '" + dir + "'", ioe);
473 return false;
474 }
475
476 boolean allFilesDeleted = true;
477 if (!files.isEmpty()) {
478 allFilesDeleted = deleteAction(new Action<Boolean>() {
479 @Override
480 public Boolean act() throws IOException {
481 return checkAndDeleteFiles(files);
482 }
483 }, "files");
484 }
485
486 boolean allSubdirsDeleted = true;
487 if (!subDirs.isEmpty()) {
488 final List<CleanerTask> tasks = Lists.newArrayListWithCapacity(subDirs.size());
489 for (FileStatus subdir : subDirs) {
490 CleanerTask task = new CleanerTask(subdir, false);
491 tasks.add(task);
492 task.fork();
493 }
494 allSubdirsDeleted = deleteAction(new Action<Boolean>() {
495 @Override
496 public Boolean act() throws IOException {
497 return getCleanResult(tasks);
498 }
499 }, "subdirs");
500 }
501
502 boolean result = allFilesDeleted && allSubdirsDeleted;
503
504
505 if (result && !root) {
506 result &= deleteAction(new Action<Boolean>() {
507 @Override
508 public Boolean act() throws IOException {
509 return fs.delete(dir, false);
510 }
511 }, "dir");
512 }
513 return result;
514 }
515
516
517
518
519
520
521
522 private boolean deleteAction(Action<Boolean> deletion, String type) {
523 boolean deleted;
524 try {
525 LOG.trace("Start deleting " + type + " under " + dir);
526 deleted = deletion.act();
527 } catch (PathIsNotEmptyDirectoryException exception) {
528
529
530
531 LOG.debug("Couldn't delete '" + dir + "' yet because it isn't empty. Probably transient. " +
532 "exception details at TRACE.");
533 LOG.trace("Couldn't delete '" + dir + "' yet because it isn't empty w/exception.",
534 exception);
535 deleted = false;
536 } catch (IOException ioe) {
537 LOG.info("Could not delete " + type + " under " + dir + ". might be transient; we'll " +
538 "retry. if it keeps happening, use following exception when asking on mailing list.",
539 ioe);
540 deleted = false;
541 }
542 LOG.trace("Finish deleting " + type + " under " + dir + " deleted=" + deleted);
543 return deleted;
544 }
545
546
547
548
549
550
551
552 private boolean getCleanResult(List<CleanerTask> tasks) throws IOException {
553 boolean cleaned = true;
554 try {
555 for (CleanerTask task : tasks) {
556 cleaned &= task.get();
557 }
558 } catch (InterruptedException | ExecutionException e) {
559 throw new IOException(e);
560 }
561 return cleaned;
562 }
563 }
564
565 }