1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.hadoop.hbase.procedure2;
20
21 import com.google.common.annotations.VisibleForTesting;
22 import com.google.common.base.Preconditions;
23
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.OutputStream;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collections;
30 import java.util.Iterator;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.HashSet;
34 import java.util.concurrent.atomic.AtomicBoolean;
35 import java.util.concurrent.atomic.AtomicInteger;
36 import java.util.concurrent.atomic.AtomicLong;
37 import java.util.concurrent.locks.ReentrantLock;
38 import java.util.concurrent.ConcurrentHashMap;
39 import java.util.concurrent.CopyOnWriteArrayList;
40 import java.util.concurrent.TimeUnit;
41
42 import org.apache.commons.logging.Log;
43 import org.apache.commons.logging.LogFactory;
44 import org.apache.hadoop.conf.Configuration;
45 import org.apache.hadoop.hbase.HConstants;
46 import org.apache.hadoop.hbase.ProcedureInfo;
47 import org.apache.hadoop.hbase.classification.InterfaceAudience;
48 import org.apache.hadoop.hbase.classification.InterfaceStability;
49 import org.apache.hadoop.hbase.exceptions.IllegalArgumentIOException;
50 import org.apache.hadoop.hbase.procedure2.store.ProcedureStore;
51 import org.apache.hadoop.hbase.procedure2.store.ProcedureStore.ProcedureIterator;
52 import org.apache.hadoop.hbase.procedure2.util.StringUtils;
53 import org.apache.hadoop.hbase.procedure2.util.TimeoutBlockingQueue;
54 import org.apache.hadoop.hbase.procedure2.util.TimeoutBlockingQueue.TimeoutRetriever;
55 import org.apache.hadoop.hbase.protobuf.generated.ErrorHandlingProtos;
56 import org.apache.hadoop.hbase.protobuf.generated.ProcedureProtos.ProcedureState;
57 import org.apache.hadoop.hbase.security.User;
58 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
59 import org.apache.hadoop.hbase.util.ForeignExceptionUtil;
60 import org.apache.hadoop.hbase.util.NonceKey;
61 import org.apache.hadoop.hbase.util.Pair;
62 import org.apache.hadoop.hbase.util.Threads;
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77 @InterfaceAudience.Private
78 @InterfaceStability.Evolving
79 public class ProcedureExecutor<TEnvironment> {
80 private static final Log LOG = LogFactory.getLog(ProcedureExecutor.class);
81
82 Testing testing = null;
83 public static class Testing {
84 protected boolean killBeforeStoreUpdate = false;
85 protected boolean toggleKillBeforeStoreUpdate = false;
86
87 protected boolean shouldKillBeforeStoreUpdate() {
88 final boolean kill = this.killBeforeStoreUpdate;
89 if (this.toggleKillBeforeStoreUpdate) {
90 this.killBeforeStoreUpdate = !kill;
91 LOG.warn("Toggle Kill before store update to: " + this.killBeforeStoreUpdate);
92 }
93 return kill;
94 }
95 }
96
97 public interface ProcedureExecutorListener {
98 void procedureLoaded(long procId);
99 void procedureAdded(long procId);
100 void procedureFinished(long procId);
101 }
102
103
104
105
106 private static class ProcedureTimeoutRetriever implements TimeoutRetriever<Procedure> {
107 @Override
108 public long getTimeout(Procedure proc) {
109 return proc.getTimeRemaining();
110 }
111
112 @Override
113 public TimeUnit getTimeUnit(Procedure proc) {
114 return TimeUnit.MILLISECONDS;
115 }
116 }
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133 private static class CompletedProcedureCleaner<TEnvironment> extends Procedure<TEnvironment> {
134 private static final Log LOG = LogFactory.getLog(CompletedProcedureCleaner.class);
135
136 private static final String CLEANER_INTERVAL_CONF_KEY = "hbase.procedure.cleaner.interval";
137 private static final int DEFAULT_CLEANER_INTERVAL = 30 * 1000;
138
139 private static final String EVICT_TTL_CONF_KEY = "hbase.procedure.cleaner.evict.ttl";
140 private static final int DEFAULT_EVICT_TTL = 15 * 60000;
141
142 private static final String EVICT_ACKED_TTL_CONF_KEY ="hbase.procedure.cleaner.acked.evict.ttl";
143 private static final int DEFAULT_ACKED_EVICT_TTL = 5 * 60000;
144
145 private final Map<Long, ProcedureInfo> completed;
146 private final Map<NonceKey, Long> nonceKeysToProcIdsMap;
147 private final ProcedureStore store;
148 private final Configuration conf;
149
150 public CompletedProcedureCleaner(final Configuration conf, final ProcedureStore store,
151 final Map<Long, ProcedureInfo> completedMap,
152 final Map<NonceKey, Long> nonceKeysToProcIdsMap) {
153
154 setTimeout(conf.getInt(CLEANER_INTERVAL_CONF_KEY, DEFAULT_CLEANER_INTERVAL));
155 this.completed = completedMap;
156 this.nonceKeysToProcIdsMap = nonceKeysToProcIdsMap;
157 this.store = store;
158 this.conf = conf;
159 }
160
161 public void periodicExecute(final TEnvironment env) {
162 if (completed.isEmpty()) {
163 if (LOG.isTraceEnabled()) {
164 LOG.trace("No completed procedures to cleanup.");
165 }
166 return;
167 }
168
169 final long evictTtl = conf.getInt(EVICT_TTL_CONF_KEY, DEFAULT_EVICT_TTL);
170 final long evictAckTtl = conf.getInt(EVICT_ACKED_TTL_CONF_KEY, DEFAULT_ACKED_EVICT_TTL);
171
172 final long now = EnvironmentEdgeManager.currentTime();
173 final Iterator<Map.Entry<Long, ProcedureInfo>> it = completed.entrySet().iterator();
174 final boolean isDebugEnabled = LOG.isDebugEnabled();
175 while (it.hasNext() && store.isRunning()) {
176 final Map.Entry<Long, ProcedureInfo> entry = it.next();
177 final ProcedureInfo procInfo = entry.getValue();
178
179
180 if ((procInfo.hasClientAckTime() && (now - procInfo.getClientAckTime()) >= evictAckTtl) ||
181 (now - procInfo.getLastUpdate()) >= evictTtl) {
182
183 if (!(procInfo instanceof FailedProcedureInfo)) {
184 store.delete(entry.getKey());
185 }
186 it.remove();
187
188 NonceKey nonceKey = procInfo.getNonceKey();
189 if (nonceKey != null) {
190 nonceKeysToProcIdsMap.remove(nonceKey);
191 }
192 if (isDebugEnabled) {
193 LOG.debug("Evict completed procedure: " + procInfo);
194 }
195 }
196 }
197 }
198
199 @Override
200 protected Procedure[] execute(final TEnvironment env) {
201 throw new UnsupportedOperationException();
202 }
203
204 @Override
205 protected void rollback(final TEnvironment env) {
206 throw new UnsupportedOperationException();
207 }
208
209 @Override
210 protected boolean abort(final TEnvironment env) {
211 throw new UnsupportedOperationException();
212 }
213
214 @Override
215 public void serializeStateData(final OutputStream stream) {
216 throw new UnsupportedOperationException();
217 }
218
219 @Override
220 public void deserializeStateData(final InputStream stream) {
221 throw new UnsupportedOperationException();
222 }
223 }
224
225
226
227
228
229
230 private final ConcurrentHashMap<Long, ProcedureInfo> completed =
231 new ConcurrentHashMap<Long, ProcedureInfo>();
232
233
234
235
236
237
238 private final ConcurrentHashMap<Long, RootProcedureState> rollbackStack =
239 new ConcurrentHashMap<Long, RootProcedureState>();
240
241
242
243
244
245 private final ConcurrentHashMap<Long, Procedure> procedures =
246 new ConcurrentHashMap<Long, Procedure>();
247
248
249
250
251
252 private ConcurrentHashMap<NonceKey, Long> nonceKeysToProcIdsMap =
253 new ConcurrentHashMap<NonceKey, Long>();
254
255
256
257
258
259 private final TimeoutBlockingQueue<Procedure> waitingTimeout =
260 new TimeoutBlockingQueue<Procedure>(new ProcedureTimeoutRetriever());
261
262
263
264
265 private final ProcedureRunnableSet runnables;
266
267
268 private final ReentrantLock submitLock = new ReentrantLock();
269 private final AtomicLong lastProcId = new AtomicLong(-1);
270
271 private final CopyOnWriteArrayList<ProcedureExecutorListener> listeners =
272 new CopyOnWriteArrayList<ProcedureExecutorListener>();
273
274 private final AtomicInteger activeExecutorCount = new AtomicInteger(0);
275 private final AtomicBoolean running = new AtomicBoolean(false);
276 private final TEnvironment environment;
277 private final ProcedureStore store;
278 private final Configuration conf;
279
280 private Thread[] threads;
281
282 public ProcedureExecutor(final Configuration conf, final TEnvironment environment,
283 final ProcedureStore store) {
284 this(conf, environment, store, new ProcedureSimpleRunQueue());
285 }
286
287 public ProcedureExecutor(final Configuration conf, final TEnvironment environment,
288 final ProcedureStore store, final ProcedureRunnableSet runqueue) {
289 this.environment = environment;
290 this.runnables = runqueue;
291 this.store = store;
292 this.conf = conf;
293 }
294
295 private void load(final boolean abortOnCorruption) throws IOException {
296 Preconditions.checkArgument(completed.isEmpty());
297 Preconditions.checkArgument(rollbackStack.isEmpty());
298 Preconditions.checkArgument(procedures.isEmpty());
299 Preconditions.checkArgument(waitingTimeout.isEmpty());
300 Preconditions.checkArgument(runnables.size() == 0);
301
302 store.load(new ProcedureStore.ProcedureLoader() {
303 @Override
304 public void setMaxProcId(long maxProcId) {
305 assert lastProcId.get() < 0 : "expected only one call to setMaxProcId()";
306 LOG.debug("load procedures maxProcId=" + maxProcId);
307 lastProcId.set(maxProcId);
308 }
309
310 @Override
311 public void load(ProcedureIterator procIter) throws IOException {
312 loadProcedures(procIter, abortOnCorruption);
313 }
314
315 @Override
316 public void handleCorrupted(ProcedureIterator procIter) throws IOException {
317 int corruptedCount = 0;
318 while (procIter.hasNext()) {
319 ProcedureInfo proc = procIter.nextAsProcedureInfo();
320 LOG.error("corrupted procedure: " + proc);
321 corruptedCount++;
322 }
323 if (abortOnCorruption && corruptedCount > 0) {
324 throw new IOException("found " + corruptedCount + " procedures on replay");
325 }
326 }
327 });
328 }
329
330 private void loadProcedures(final ProcedureIterator procIter,
331 final boolean abortOnCorruption) throws IOException {
332 final boolean isDebugEnabled = LOG.isDebugEnabled();
333
334
335 int runnablesCount = 0;
336 while (procIter.hasNext()) {
337 final NonceKey nonceKey;
338 final long procId;
339
340 if (procIter.isNextCompleted()) {
341 ProcedureInfo proc = procIter.nextAsProcedureInfo();
342 nonceKey = proc.getNonceKey();
343 procId = proc.getProcId();
344 completed.put(proc.getProcId(), proc);
345 if (isDebugEnabled) {
346 LOG.debug("The procedure is completed: " + proc);
347 }
348 } else {
349 Procedure proc = procIter.nextAsProcedure();
350 nonceKey = proc.getNonceKey();
351 procId = proc.getProcId();
352
353 if (!proc.hasParent()) {
354 assert !proc.isFinished() : "unexpected finished procedure";
355 rollbackStack.put(proc.getProcId(), new RootProcedureState());
356 }
357
358
359 proc.beforeReplay(getEnvironment());
360 procedures.put(proc.getProcId(), proc);
361
362 if (proc.getState() == ProcedureState.RUNNABLE) {
363 runnablesCount++;
364 }
365 }
366
367
368 if (nonceKey != null) {
369 nonceKeysToProcIdsMap.put(nonceKey, procId);
370 }
371 }
372
373
374 ArrayList<Procedure> runnableList = new ArrayList(runnablesCount);
375 HashSet<Procedure> waitingSet = null;
376 procIter.reset();
377 while (procIter.hasNext()) {
378 if (procIter.isNextCompleted()) {
379 procIter.skipNext();
380 continue;
381 }
382
383 Procedure proc = procIter.nextAsProcedure();
384 assert !(proc.isFinished() && !proc.hasParent()) : "unexpected completed proc=" + proc;
385
386 if (isDebugEnabled) {
387 LOG.debug(String.format("Loading procedure state=%s isFailed=%s: %s",
388 proc.getState(), proc.hasException(), proc));
389 }
390
391 Long rootProcId = getRootProcedureId(proc);
392 if (rootProcId == null) {
393
394 runnables.addBack(proc);
395 continue;
396 }
397
398 if (proc.hasParent() && !proc.isFinished()) {
399 Procedure parent = procedures.get(proc.getParentProcId());
400
401 if (parent != null) {
402 parent.incChildrenLatch();
403 }
404 }
405
406 RootProcedureState procStack = rollbackStack.get(rootProcId);
407 procStack.loadStack(proc);
408
409 switch (proc.getState()) {
410 case RUNNABLE:
411 runnableList.add(proc);
412 break;
413 case WAITING_TIMEOUT:
414 if (waitingSet == null) {
415 waitingSet = new HashSet<Procedure>();
416 }
417 waitingSet.add(proc);
418 break;
419 case FINISHED:
420 if (proc.hasException()) {
421
422 runnables.addBack(proc);
423 break;
424 }
425 case ROLLEDBACK:
426 case INITIALIZING:
427 String msg = "Unexpected " + proc.getState() + " state for " + proc;
428 LOG.error(msg);
429 throw new UnsupportedOperationException(msg);
430 default:
431 break;
432 }
433 }
434
435
436 int corruptedCount = 0;
437 Iterator<Map.Entry<Long, RootProcedureState>> itStack = rollbackStack.entrySet().iterator();
438 while (itStack.hasNext()) {
439 Map.Entry<Long, RootProcedureState> entry = itStack.next();
440 RootProcedureState procStack = entry.getValue();
441 if (procStack.isValid()) continue;
442
443 for (Procedure proc: procStack.getSubprocedures()) {
444 LOG.error("corrupted procedure: " + proc);
445 procedures.remove(proc.getProcId());
446 runnableList.remove(proc);
447 if (waitingSet != null) waitingSet.remove(proc);
448 corruptedCount++;
449 }
450 itStack.remove();
451 }
452
453 if (abortOnCorruption && corruptedCount > 0) {
454 throw new IOException("found " + corruptedCount + " procedures on replay");
455 }
456
457
458 if (!runnableList.isEmpty()) {
459
460
461 for (int i = runnableList.size() - 1; i >= 0; --i) {
462 Procedure proc = runnableList.get(i);
463 if (!proc.hasParent()) {
464 sendProcedureLoadedNotification(proc.getProcId());
465 }
466 if (proc.wasExecuted()) {
467 runnables.addFront(proc);
468 } else {
469
470 runnables.addBack(proc);
471 }
472 }
473 }
474 }
475
476
477
478
479
480
481
482
483
484
485
486 public void start(int numThreads, boolean abortOnCorruption) throws IOException {
487 if (running.getAndSet(true)) {
488 LOG.warn("Already running");
489 return;
490 }
491
492
493
494 threads = new Thread[numThreads + 1];
495 LOG.info("Starting procedure executor threads=" + threads.length);
496
497
498 for (int i = 0; i < numThreads; ++i) {
499 threads[i] = new Thread("ProcedureExecutor-" + i) {
500 @Override
501 public void run() {
502 execLoop();
503 }
504 };
505 }
506
507
508 threads[numThreads] = new Thread("ProcedureExecutorTimeout") {
509 @Override
510 public void run() {
511 timeoutLoop();
512 }
513 };
514
515
516 store.recoverLease();
517
518
519
520
521
522
523 load(abortOnCorruption);
524
525
526 for (int i = 0; i < threads.length; ++i) {
527 threads[i].start();
528 }
529
530
531 waitingTimeout.add(
532 new CompletedProcedureCleaner(conf, store, completed, nonceKeysToProcIdsMap));
533 }
534
535 public void stop() {
536 if (!running.getAndSet(false)) {
537 return;
538 }
539
540 LOG.info("Stopping the procedure executor");
541 runnables.signalAll();
542 waitingTimeout.signalAll();
543 }
544
545 public void join() {
546 boolean interrupted = false;
547
548 for (int i = 0; i < threads.length; ++i) {
549 try {
550 threads[i].join();
551 } catch (InterruptedException ex) {
552 interrupted = true;
553 }
554 }
555
556 if (interrupted) {
557 Thread.currentThread().interrupt();
558 }
559
560 completed.clear();
561 rollbackStack.clear();
562 procedures.clear();
563 nonceKeysToProcIdsMap.clear();
564 waitingTimeout.clear();
565 runnables.clear();
566 lastProcId.set(-1);
567 }
568
569 public boolean isRunning() {
570 return running.get();
571 }
572
573
574
575
576 public int getNumThreads() {
577 return threads == null ? 0 : (threads.length - 1);
578 }
579
580 public int getActiveExecutorCount() {
581 return activeExecutorCount.get();
582 }
583
584 public TEnvironment getEnvironment() {
585 return this.environment;
586 }
587
588 public ProcedureStore getStore() {
589 return this.store;
590 }
591
592 public void registerListener(ProcedureExecutorListener listener) {
593 this.listeners.add(listener);
594 }
595
596 public boolean unregisterListener(ProcedureExecutorListener listener) {
597 return this.listeners.remove(listener);
598 }
599
600
601
602
603
604 public List<ProcedureInfo> listProcedures() {
605 List<ProcedureInfo> procedureLists =
606 new ArrayList<ProcedureInfo>(procedures.size() + completed.size());
607 for (java.util.Map.Entry<Long, Procedure> p: procedures.entrySet()) {
608 procedureLists.add(Procedure.createProcedureInfo(p.getValue(), null));
609 }
610 for (java.util.Map.Entry<Long, ProcedureInfo> e: completed.entrySet()) {
611
612
613
614
615 procedureLists.add(e.getValue());
616 }
617 return procedureLists;
618 }
619
620
621
622
623
624
625
626
627
628
629 public NonceKey createNonceKey(final long nonceGroup, final long nonce) {
630 return (nonce == HConstants.NO_NONCE) ? null : new NonceKey(nonceGroup, nonce);
631 }
632
633
634
635
636
637
638
639
640
641
642
643
644 public long registerNonce(final NonceKey nonceKey) {
645 if (nonceKey == null) return -1;
646
647
648 Long oldProcId = nonceKeysToProcIdsMap.get(nonceKey);
649 if (oldProcId == null) {
650
651
652 final long newProcId = nextProcId();
653 oldProcId = nonceKeysToProcIdsMap.putIfAbsent(nonceKey, newProcId);
654 if (oldProcId == null) return -1;
655 }
656
657
658
659 final boolean isTraceEnabled = LOG.isTraceEnabled();
660 while (isRunning() &&
661 !(procedures.containsKey(oldProcId) || completed.containsKey(oldProcId)) &&
662 nonceKeysToProcIdsMap.containsKey(nonceKey)) {
663 if (isTraceEnabled) {
664 LOG.trace("waiting for procId=" + oldProcId.longValue() + " to be submitted");
665 }
666 Threads.sleep(100);
667 }
668 return oldProcId.longValue();
669 }
670
671
672
673
674
675 public void unregisterNonceIfProcedureWasNotSubmitted(final NonceKey nonceKey) {
676 if (nonceKey == null) return;
677
678 final Long procId = nonceKeysToProcIdsMap.get(nonceKey);
679 if (procId == null) return;
680
681
682 if (!(procedures.containsKey(procId) || completed.containsKey(procId))) {
683 nonceKeysToProcIdsMap.remove(nonceKey);
684 }
685 }
686
687
688
689
690
691
692
693
694
695
696 public void setFailureResultForNonce(final NonceKey nonceKey, final String procName,
697 final User procOwner, final IOException exception) {
698 if (nonceKey == null) return;
699
700 final Long procId = nonceKeysToProcIdsMap.get(nonceKey);
701 if (procId == null || completed.containsKey(procId)) return;
702
703 final long currentTime = EnvironmentEdgeManager.currentTime();
704 final ProcedureInfo result = new FailedProcedureInfo(
705 procId.longValue(),
706 procName,
707 procOwner != null ? procOwner.getShortName() : null,
708 ProcedureState.ROLLEDBACK,
709 -1,
710 nonceKey,
711 ForeignExceptionUtil.toProtoForeignException("ProcedureExecutor", exception),
712 currentTime,
713 currentTime,
714 null);
715 completed.putIfAbsent(procId, result);
716 }
717
718 public static class FailedProcedureInfo extends ProcedureInfo {
719
720 public FailedProcedureInfo(long procId, String procName, String procOwner,
721 ProcedureState procState, long parentId, NonceKey nonceKey,
722 ErrorHandlingProtos.ForeignExceptionMessage exception, long lastUpdate, long startTime,
723 byte[] result) {
724 super(procId, procName, procOwner, procState, parentId, nonceKey, exception, lastUpdate,
725 startTime, result);
726 }
727 }
728
729
730
731
732
733
734
735
736
737
738 public long submitProcedure(final Procedure proc) {
739 return submitProcedure(proc, null);
740 }
741
742
743
744
745
746
747
748 @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="NP_NULL_ON_SOME_PATH",
749 justification = "FindBugs is blind to the check-for-null")
750 public long submitProcedure(final Procedure proc, final NonceKey nonceKey) {
751 Preconditions.checkArgument(proc.getState() == ProcedureState.INITIALIZING);
752 Preconditions.checkArgument(isRunning(), "executor not running");
753 Preconditions.checkArgument(lastProcId.get() >= 0);
754 Preconditions.checkArgument(!proc.hasParent(), "unexpected parent", proc);
755
756 final Long currentProcId;
757 if (nonceKey != null) {
758 currentProcId = nonceKeysToProcIdsMap.get(nonceKey);
759 Preconditions.checkArgument(currentProcId != null,
760 "expected nonceKey=" + nonceKey + " to be reserved, use registerNonce()");
761 } else {
762 currentProcId = nextProcId();
763 }
764
765
766 proc.setNonceKey(nonceKey);
767 proc.setProcId(currentProcId.longValue());
768
769
770 store.insert(proc, null);
771 if (LOG.isDebugEnabled()) {
772 LOG.debug("Procedure " + proc + " added to the store.");
773 }
774
775
776 RootProcedureState stack = new RootProcedureState();
777 rollbackStack.put(currentProcId, stack);
778
779
780 assert !procedures.containsKey(currentProcId);
781 procedures.put(currentProcId, proc);
782 sendProcedureAddedNotification(currentProcId);
783 runnables.addBack(proc);
784 return currentProcId;
785 }
786
787 public ProcedureInfo getResult(final long procId) {
788 return completed.get(procId);
789 }
790
791
792
793
794
795
796
797
798 public boolean isFinished(final long procId) {
799 return completed.containsKey(procId);
800 }
801
802
803
804
805
806
807 public boolean isStarted(final long procId) {
808 Procedure proc = procedures.get(procId);
809 if (proc == null) {
810 return completed.get(procId) != null;
811 }
812 return proc.wasExecuted();
813 }
814
815
816
817
818
819 public void removeResult(final long procId) {
820 ProcedureInfo result = completed.get(procId);
821 if (result == null) {
822 assert !procedures.containsKey(procId) : "procId=" + procId + " is still running";
823 if (LOG.isDebugEnabled()) {
824 LOG.debug("Procedure procId=" + procId + " already removed by the cleaner.");
825 }
826 return;
827 }
828
829
830 result.setClientAckTime(EnvironmentEdgeManager.currentTime());
831 }
832
833
834
835
836
837
838
839 public boolean abort(final long procId) {
840 return abort(procId, true);
841 }
842
843
844
845
846
847
848
849
850 public boolean abort(final long procId, final boolean mayInterruptIfRunning) {
851 Procedure proc = procedures.get(procId);
852 if (proc != null) {
853 if (!mayInterruptIfRunning && proc.wasExecuted()) {
854 return false;
855 } else {
856 return proc.abort(getEnvironment());
857 }
858 }
859 return false;
860 }
861
862
863
864
865
866
867
868
869 public boolean isProcedureOwner(final long procId, final User user) {
870 if (user == null) {
871 return false;
872 }
873
874 Procedure proc = procedures.get(procId);
875 if (proc != null) {
876 return proc.getOwner().equals(user.getShortName());
877 }
878 ProcedureInfo procInfo = completed.get(procId);
879 if (procInfo == null) {
880
881
882 return false;
883 }
884 return ProcedureInfo.isProcedureOwner(procInfo, user);
885 }
886
887 public Map<Long, ProcedureInfo> getResults() {
888 return Collections.unmodifiableMap(completed);
889 }
890
891 public Procedure getProcedure(final long procId) {
892 return procedures.get(procId);
893 }
894
895 protected ProcedureRunnableSet getRunnableSet() {
896 return runnables;
897 }
898
899
900
901
902
903
904 private void execLoop() {
905 while (isRunning()) {
906 Procedure proc = runnables.poll();
907 if (proc == null) continue;
908
909 try {
910 activeExecutorCount.incrementAndGet();
911 execLoop(proc);
912 } finally {
913 activeExecutorCount.decrementAndGet();
914 }
915 }
916 }
917
918 private void execLoop(Procedure proc) {
919 if (LOG.isTraceEnabled()) {
920 LOG.trace("Trying to start the execution of " + proc);
921 }
922
923 Long rootProcId = getRootProcedureId(proc);
924 if (rootProcId == null) {
925
926 executeRollback(proc);
927 return;
928 }
929
930 RootProcedureState procStack = rollbackStack.get(rootProcId);
931 if (procStack == null) return;
932
933 do {
934
935 if (!procStack.acquire(proc)) {
936 if (procStack.setRollback()) {
937
938 if (!executeRollback(rootProcId, procStack)) {
939 procStack.unsetRollback();
940 runnables.yield(proc);
941 }
942 } else {
943
944
945
946 if (!proc.wasExecuted()) {
947 if (!executeRollback(proc)) {
948 runnables.yield(proc);
949 }
950 }
951 }
952 break;
953 }
954
955
956 assert proc.getState() == ProcedureState.RUNNABLE;
957 if (proc.acquireLock(getEnvironment())) {
958 execProcedure(procStack, proc);
959 proc.releaseLock(getEnvironment());
960 } else {
961 runnables.yield(proc);
962 }
963 procStack.release(proc);
964
965
966
967 if (testing != null && !isRunning()) {
968 break;
969 }
970
971 if (proc.isSuccess()) {
972 if (LOG.isDebugEnabled()) {
973 LOG.debug("Procedure completed in " +
974 StringUtils.humanTimeDiff(proc.elapsedTime()) + ": " + proc);
975 }
976
977 if (proc.getProcId() == rootProcId) {
978 procedureFinished(proc);
979 }
980 break;
981 }
982 } while (procStack.isFailed());
983 }
984
985 private void timeoutLoop() {
986 while (isRunning()) {
987 Procedure proc = waitingTimeout.poll();
988 if (proc == null) continue;
989
990 if (proc.getTimeRemaining() > 100) {
991
992
993 waitingTimeout.add(proc);
994 continue;
995 }
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008 if (proc instanceof CompletedProcedureCleaner) {
1009 try {
1010 ((CompletedProcedureCleaner)proc).periodicExecute(getEnvironment());
1011 } catch (Throwable e) {
1012 LOG.error("Ignoring CompletedProcedureCleaner exception: " + e.getMessage(), e);
1013 }
1014 proc.setStartTime(EnvironmentEdgeManager.currentTime());
1015 waitingTimeout.add(proc);
1016 continue;
1017 }
1018
1019
1020
1021 if (proc.setTimeoutFailure()) {
1022 long rootProcId = Procedure.getRootProcedureId(procedures, proc);
1023 RootProcedureState procStack = rollbackStack.get(rootProcId);
1024 procStack.abort();
1025 store.update(proc);
1026 runnables.addFront(proc);
1027 continue;
1028 }
1029 }
1030 }
1031
1032
1033
1034
1035
1036
1037 private boolean executeRollback(final long rootProcId, final RootProcedureState procStack) {
1038 Procedure rootProc = procedures.get(rootProcId);
1039 RemoteProcedureException exception = rootProc.getException();
1040 if (exception == null) {
1041 exception = procStack.getException();
1042 rootProc.setFailure(exception);
1043 store.update(rootProc);
1044 }
1045
1046 List<Procedure> subprocStack = procStack.getSubprocedures();
1047 assert subprocStack != null : "Called rollback with no steps executed rootProc=" + rootProc;
1048
1049 int stackTail = subprocStack.size();
1050 boolean reuseLock = false;
1051 while (stackTail --> 0) {
1052 final Procedure proc = subprocStack.get(stackTail);
1053
1054 if (!reuseLock && !proc.acquireLock(getEnvironment())) {
1055
1056
1057 return false;
1058 }
1059
1060 boolean abortRollback = !executeRollback(proc);
1061 abortRollback |= !isRunning() || !store.isRunning();
1062
1063
1064
1065
1066 reuseLock = stackTail > 0 && (subprocStack.get(stackTail - 1) == proc) && !abortRollback;
1067 if (!reuseLock) {
1068 proc.releaseLock(getEnvironment());
1069 }
1070
1071
1072
1073 if (abortRollback) {
1074 return false;
1075 }
1076
1077 subprocStack.remove(stackTail);
1078
1079
1080 if (proc.isYieldAfterExecutionStep(getEnvironment())) {
1081 return false;
1082 }
1083 }
1084
1085
1086 LOG.info("Rolledback procedure " + rootProc +
1087 " exec-time=" + StringUtils.humanTimeDiff(rootProc.elapsedTime()) +
1088 " exception=" + exception.getMessage());
1089 procedureFinished(rootProc);
1090 return true;
1091 }
1092
1093
1094
1095
1096
1097
1098 private boolean executeRollback(final Procedure proc) {
1099 try {
1100 proc.doRollback(getEnvironment());
1101 } catch (IOException e) {
1102 if (LOG.isDebugEnabled()) {
1103 LOG.debug("rollback attempt failed for " + proc, e);
1104 }
1105 return false;
1106 } catch (InterruptedException e) {
1107 handleInterruptedException(proc, e);
1108 return false;
1109 } catch (Throwable e) {
1110
1111 LOG.fatal("CODE-BUG: Uncatched runtime exception for procedure: " + proc, e);
1112 }
1113
1114
1115
1116 if (testing != null && testing.shouldKillBeforeStoreUpdate()) {
1117 LOG.debug("TESTING: Kill before store update");
1118 stop();
1119 return false;
1120 }
1121
1122 if (proc.removeStackIndex()) {
1123 proc.setState(ProcedureState.ROLLEDBACK);
1124 if (proc.hasParent()) {
1125 store.delete(proc.getProcId());
1126 procedures.remove(proc.getProcId());
1127 } else {
1128 store.update(proc);
1129 }
1130 } else {
1131 store.update(proc);
1132 }
1133
1134 return true;
1135 }
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154 private void execProcedure(final RootProcedureState procStack, final Procedure procedure) {
1155 Preconditions.checkArgument(procedure.getState() == ProcedureState.RUNNABLE);
1156
1157
1158 boolean reExecute = false;
1159 Procedure[] subprocs = null;
1160 do {
1161 reExecute = false;
1162 try {
1163 subprocs = procedure.doExecute(getEnvironment());
1164 if (subprocs != null && subprocs.length == 0) {
1165 subprocs = null;
1166 }
1167 } catch (ProcedureYieldException e) {
1168 if (LOG.isTraceEnabled()) {
1169 LOG.trace("Yield procedure: " + procedure + ": " + e.getMessage());
1170 }
1171 runnables.yield(procedure);
1172 return;
1173 } catch (InterruptedException e) {
1174 handleInterruptedException(procedure, e);
1175 runnables.yield(procedure);
1176 return;
1177 } catch (Throwable e) {
1178
1179 String msg = "CODE-BUG: Uncatched runtime exception for procedure: " + procedure;
1180 LOG.error(msg, e);
1181 procedure.setFailure(new RemoteProcedureException(msg, e));
1182 }
1183
1184 if (!procedure.isFailed()) {
1185 if (subprocs != null) {
1186 if (subprocs.length == 1 && subprocs[0] == procedure) {
1187
1188 subprocs = null;
1189 reExecute = true;
1190 } else {
1191
1192 for (int i = 0; i < subprocs.length; ++i) {
1193 Procedure subproc = subprocs[i];
1194 if (subproc == null) {
1195 String msg = "subproc[" + i + "] is null, aborting the procedure";
1196 procedure.setFailure(new RemoteProcedureException(msg,
1197 new IllegalArgumentIOException(msg)));
1198 subprocs = null;
1199 break;
1200 }
1201
1202 assert subproc.getState() == ProcedureState.INITIALIZING;
1203 subproc.setParentProcId(procedure.getProcId());
1204 subproc.setProcId(nextProcId());
1205 }
1206
1207 if (!procedure.isFailed()) {
1208 procedure.setChildrenLatch(subprocs.length);
1209 switch (procedure.getState()) {
1210 case RUNNABLE:
1211 procedure.setState(ProcedureState.WAITING);
1212 break;
1213 case WAITING_TIMEOUT:
1214 waitingTimeout.add(procedure);
1215 break;
1216 default:
1217 break;
1218 }
1219 }
1220 }
1221 } else if (procedure.getState() == ProcedureState.WAITING_TIMEOUT) {
1222 waitingTimeout.add(procedure);
1223 } else {
1224
1225 procedure.setState(ProcedureState.FINISHED);
1226 }
1227 }
1228
1229
1230 procStack.addRollbackStep(procedure);
1231
1232
1233
1234 if (testing != null && testing.shouldKillBeforeStoreUpdate()) {
1235 LOG.debug("TESTING: Kill before store update");
1236 stop();
1237 return;
1238 }
1239
1240
1241 if (subprocs != null && !procedure.isFailed()) {
1242 if (LOG.isTraceEnabled()) {
1243 LOG.trace("Store add " + procedure + " children " + Arrays.toString(subprocs));
1244 }
1245 store.insert(procedure, subprocs);
1246 } else {
1247 if (LOG.isTraceEnabled()) {
1248 LOG.trace("Store update " + procedure);
1249 }
1250 store.update(procedure);
1251 }
1252
1253
1254 if (!store.isRunning()) {
1255 return;
1256 }
1257
1258
1259 if (procedure.getState() == ProcedureState.RUNNABLE &&
1260 procedure.isYieldAfterExecutionStep(getEnvironment())) {
1261 runnables.yield(procedure);
1262 return;
1263 }
1264
1265 assert (reExecute && subprocs == null) || !reExecute;
1266 } while (reExecute);
1267
1268
1269 if (subprocs != null && !procedure.isFailed()) {
1270 for (int i = 0; i < subprocs.length; ++i) {
1271 Procedure subproc = subprocs[i];
1272 assert !procedures.containsKey(subproc.getProcId());
1273 procedures.put(subproc.getProcId(), subproc);
1274 runnables.addFront(subproc);
1275 }
1276 }
1277
1278 if (procedure.isFinished() && procedure.hasParent()) {
1279 Procedure parent = procedures.get(procedure.getParentProcId());
1280 if (parent == null) {
1281 assert procStack.isRollingback();
1282 return;
1283 }
1284
1285
1286 if (LOG.isTraceEnabled()) {
1287 LOG.trace(parent + " child is done: " + procedure);
1288 }
1289 if (parent.childrenCountDown() && parent.getState() == ProcedureState.WAITING) {
1290 parent.setState(ProcedureState.RUNNABLE);
1291 store.update(parent);
1292 runnables.addFront(parent);
1293 if (LOG.isTraceEnabled()) {
1294 LOG.trace(parent + " all the children finished their work, resume.");
1295 }
1296 return;
1297 }
1298 }
1299 }
1300
1301 private void handleInterruptedException(final Procedure proc, final InterruptedException e) {
1302 if (LOG.isTraceEnabled()) {
1303 LOG.trace("got an interrupt during " + proc + ". suspend and retry it later.", e);
1304 }
1305
1306
1307
1308
1309
1310
1311 }
1312
1313 private void sendProcedureLoadedNotification(final long procId) {
1314 if (!this.listeners.isEmpty()) {
1315 for (ProcedureExecutorListener listener: this.listeners) {
1316 try {
1317 listener.procedureLoaded(procId);
1318 } catch (Throwable e) {
1319 LOG.error("The listener " + listener + " had an error: " + e.getMessage(), e);
1320 }
1321 }
1322 }
1323 }
1324
1325 private void sendProcedureAddedNotification(final long procId) {
1326 if (!this.listeners.isEmpty()) {
1327 for (ProcedureExecutorListener listener: this.listeners) {
1328 try {
1329 listener.procedureAdded(procId);
1330 } catch (Throwable e) {
1331 LOG.error("The listener " + listener + " had an error: " + e.getMessage(), e);
1332 }
1333 }
1334 }
1335 }
1336
1337 private void sendProcedureFinishedNotification(final long procId) {
1338 if (!this.listeners.isEmpty()) {
1339 for (ProcedureExecutorListener listener: this.listeners) {
1340 try {
1341 listener.procedureFinished(procId);
1342 } catch (Throwable e) {
1343 LOG.error("The listener " + listener + " had an error: " + e.getMessage(), e);
1344 }
1345 }
1346 }
1347 }
1348
1349 private long nextProcId() {
1350 long procId = lastProcId.incrementAndGet();
1351 if (procId < 0) {
1352 while (!lastProcId.compareAndSet(procId, 0)) {
1353 procId = lastProcId.get();
1354 if (procId >= 0)
1355 break;
1356 }
1357 while (procedures.containsKey(procId)) {
1358 procId = lastProcId.incrementAndGet();
1359 }
1360 }
1361 return procId;
1362 }
1363
1364 @VisibleForTesting
1365 protected long getLastProcId() {
1366 return lastProcId.get();
1367 }
1368
1369 private Long getRootProcedureId(Procedure proc) {
1370 return Procedure.getRootProcedureId(procedures, proc);
1371 }
1372
1373 private void procedureFinished(final Procedure proc) {
1374
1375 try {
1376 proc.completionCleanup(getEnvironment());
1377 } catch (Throwable e) {
1378
1379 LOG.error("CODE-BUG: uncatched runtime exception for procedure: " + proc, e);
1380 }
1381
1382
1383 ProcedureInfo procInfo = Procedure.createProcedureInfo(proc, proc.getNonceKey());
1384 if (!proc.shouldWaitClientAck(getEnvironment())) {
1385 procInfo.setClientAckTime(0);
1386 }
1387
1388 completed.put(procInfo.getProcId(), procInfo);
1389 rollbackStack.remove(proc.getProcId());
1390 procedures.remove(proc.getProcId());
1391
1392
1393 try {
1394 runnables.completionCleanup(proc);
1395 } catch (Throwable e) {
1396
1397 LOG.error("CODE-BUG: uncatched runtime exception for runnableSet: " + runnables, e);
1398 }
1399
1400
1401 sendProcedureFinishedNotification(proc.getProcId());
1402 }
1403
1404 public Pair<ProcedureInfo, Procedure> getResultOrProcedure(final long procId) {
1405 ProcedureInfo result = completed.get(procId);
1406 Procedure proc = null;
1407 if (result == null) {
1408 proc = procedures.get(procId);
1409 if (proc == null) {
1410 result = completed.get(procId);
1411 }
1412 }
1413 return new Pair(result, proc);
1414 }
1415 }