View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
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   * Thread Pool that executes the submitted procedures.
66   * The executor has a ProcedureStore associated.
67   * Each operation is logged and on restart the pending procedures are resumed.
68   *
69   * Unless the Procedure code throws an error (e.g. invalid user input)
70   * the procedure will complete (at some point in time), On restart the pending
71   * procedures are resumed and the once failed will be rolledback.
72   *
73   * The user can add procedures to the executor via submitProcedure(proc)
74   * check for the finished state via isFinished(procId)
75   * and get the result via getResult(procId)
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    * Used by the TimeoutBlockingQueue to get the timeout interval of the procedure
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    * Internal cleaner that removes the completed procedure results after a TTL.
120    * NOTE: This is a special case handled in timeoutLoop().
121    *
122    * Since the client code looks more or less like:
123    *   procId = master.doOperation()
124    *   while (master.getProcResult(procId) == ProcInProgress);
125    * The master should not throw away the proc result as soon as the procedure is done
126    * but should wait a result request from the client (see executor.removeResult(procId))
127    * The client will call something like master.isProcDone() or master.getProcResult()
128    * which will return the result/state to the client, and it will mark the completed
129    * proc as ready to delete. note that the client may not receive the response from
130    * the master (e.g. master failover) so, if we delay a bit the real deletion of
131    * the proc result the client will be able to get the result the next try.
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; // 30sec
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; // 15min
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; // 5min
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       // set the timeout interval that triggers the periodic-procedure
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         // TODO: Select TTL based on Procedure type
180         if ((procInfo.hasClientAckTime() && (now - procInfo.getClientAckTime()) >= evictAckTtl) ||
181             (now - procInfo.getLastUpdate()) >= evictTtl) {
182           // Failed Procedures aren't persisted in WAL.
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    * Map the the procId returned by submitProcedure(), the Root-ProcID, to the ProcedureInfo.
227    * Once a Root-Procedure completes (success or failure), the result will be added to this map.
228    * The user of ProcedureExecutor should call getResult(procId) to get the result.
229    */
230   private final ConcurrentHashMap<Long, ProcedureInfo> completed =
231     new ConcurrentHashMap<Long, ProcedureInfo>();
232 
233   /**
234    * Map the the procId returned by submitProcedure(), the Root-ProcID, to the RootProcedureState.
235    * The RootProcedureState contains the execution stack of the Root-Procedure,
236    * It is added to the map by submitProcedure() and removed on procedure completion.
237    */
238   private final ConcurrentHashMap<Long, RootProcedureState> rollbackStack =
239     new ConcurrentHashMap<Long, RootProcedureState>();
240 
241   /**
242    * Helper map to lookup the live procedures by ID.
243    * This map contains every procedure. root-procedures and subprocedures.
244    */
245   private final ConcurrentHashMap<Long, Procedure> procedures =
246     new ConcurrentHashMap<Long, Procedure>();
247 
248   /**
249    * Helper map to lookup whether the procedure already issued from the same client.
250    * This map contains every root procedure.
251    */
252   private ConcurrentHashMap<NonceKey, Long> nonceKeysToProcIdsMap =
253       new ConcurrentHashMap<NonceKey, Long>();
254 
255   /**
256    * Timeout Queue that contains Procedures in a WAITING_TIMEOUT state
257    * or periodic procedures.
258    */
259   private final TimeoutBlockingQueue<Procedure> waitingTimeout =
260     new TimeoutBlockingQueue<Procedure>(new ProcedureTimeoutRetriever());
261 
262   /**
263    * Queue that contains runnable procedures.
264    */
265   private final ProcedureRunnableSet runnables;
266 
267   // TODO
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     // 1. Build the rollback stack
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         // add the procedure to the map
359         proc.beforeReplay(getEnvironment());
360         procedures.put(proc.getProcId(), proc);
361 
362         if (proc.getState() == ProcedureState.RUNNABLE) {
363           runnablesCount++;
364         }
365       }
366 
367       // add the nonce to the map
368       if (nonceKey != null) {
369         nonceKeysToProcIdsMap.put(nonceKey, procId);
370       }
371     }
372 
373     // 2. Initialize the stacks
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         // The 'proc' was ready to run but the root procedure was rolledback?
394         runnables.addBack(proc);
395         continue;
396       }
397 
398       if (proc.hasParent() && !proc.isFinished()) {
399         Procedure parent = procedures.get(proc.getParentProcId());
400         // corrupted procedures are handled later at step 3
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             // add the proc to the runnables to perform the rollback
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     // 3. Validate the stacks
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     // 4. Push the runnables
458     if (!runnableList.isEmpty()) {
459       // TODO: See ProcedureWALFormatReader#hasFastStartSupport
460       // some procedure may be started way before this stuff.
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           // if it was not in execution, it can wait.
470           runnables.addBack(proc);
471         }
472       }
473     }
474   }
475 
476   /**
477    * Start the procedure executor.
478    * It calls ProcedureStore.recoverLease() and ProcedureStore.load() to
479    * recover the lease, and ensure a single executor, and start the procedure
480    * replay to resume and recover the previous pending and in-progress perocedures.
481    *
482    * @param numThreads number of threads available for procedure execution.
483    * @param abortOnCorruption true if you want to abort your service in case
484    *          a corrupted procedure is found on replay. otherwise false.
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     // We have numThreads executor + one timer thread used for timing out
493     // procedures and triggering periodic procedures.
494     threads = new Thread[numThreads + 1];
495     LOG.info("Starting procedure executor threads=" + threads.length);
496 
497     // Initialize procedures executor
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     // Initialize procedures timeout handler (this is the +1 thread)
508     threads[numThreads] = new Thread("ProcedureExecutorTimeout") {
509       @Override
510       public void run() {
511         timeoutLoop();
512       }
513     };
514 
515     // Acquire the store lease.
516     store.recoverLease();
517 
518     // TODO: Split in two steps.
519     // TODO: Handle corrupted procedures (currently just a warn)
520     // The first one will make sure that we have the latest id,
521     // so we can start the threads and accept new procedures.
522     // The second step will do the actual load of old procedures.
523     load(abortOnCorruption);
524 
525     // Start the executors. Here we must have the lastProcId set.
526     for (int i = 0; i < threads.length; ++i) {
527       threads[i].start();
528     }
529 
530     // Add completed cleaner
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    * @return the number of execution threads.
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    * List procedures.
602    * @return the procedures in a list
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       // Note: The procedure could show up twice in the list with different state, as
612       // it could complete after we walk through procedures list and insert into
613       // procedureList - it is ok, as we will use the information in the ProcedureInfo
614       // to figure it out; to prevent this would increase the complexity of the logic.
615       procedureLists.add(e.getValue());
616     }
617     return procedureLists;
618   }
619 
620   // ==========================================================================
621   //  Nonce Procedure helpers
622   // ==========================================================================
623   /**
624    * Create a NoneKey from the specified nonceGroup and nonce.
625    * @param nonceGroup
626    * @param nonce
627    * @return the generated NonceKey
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    * Register a nonce for a procedure that is going to be submitted.
635    * A procId will be reserved and on submitProcedure(),
636    * the procedure with the specified nonce will take the reserved ProcId.
637    * If someone already reserved the nonce, this method will return the procId reserved,
638    * otherwise an invalid procId will be returned. and the caller should procede
639    * and submit the procedure.
640    *
641    * @param nonceKey A unique identifier for this operation from the client or process.
642    * @return the procId associated with the nonce, if any otherwise an invalid procId.
643    */
644   public long registerNonce(final NonceKey nonceKey) {
645     if (nonceKey == null) return -1;
646 
647     // check if we have already a Reserved ID for the nonce
648     Long oldProcId = nonceKeysToProcIdsMap.get(nonceKey);
649     if (oldProcId == null) {
650       // reserve a new Procedure ID, this will be associated with the nonce
651       // and the procedure submitted with the specified nonce will use this ID.
652       final long newProcId = nextProcId();
653       oldProcId = nonceKeysToProcIdsMap.putIfAbsent(nonceKey, newProcId);
654       if (oldProcId == null) return -1;
655     }
656 
657     // we found a registered nonce, but the procedure may not have been submitted yet.
658     // since the client expect the procedure to be submitted, spin here until it is.
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    * Remove the NonceKey if the procedure was not submitted to the executor.
673    * @param nonceKey A unique identifier for this operation from the client or process.
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     // if the procedure was not submitted, remove the nonce
682     if (!(procedures.containsKey(procId) || completed.containsKey(procId))) {
683       nonceKeysToProcIdsMap.remove(nonceKey);
684     }
685   }
686 
687   /**
688    * If the failure failed before submitting it, we may want to give back the
689    * same error to the requests with the same nonceKey.
690    *
691    * @param nonceKey A unique identifier for this operation from the client or process
692    * @param procName name of the procedure, used to inform the user
693    * @param procOwner name of the owner of the procedure, used to inform the user
694    * @param exception the failure to report to the user
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   //  Submit/Abort Procedure
731   // ==========================================================================
732   /**
733 >>>>>>> ce33cf2... HBASE-17149 Procedure V2 - Fix nonce submission to avoid unnecessary calling coprocessor multiple times (Matteo Bertozzi)
734    * Add a new root-procedure to the executor.
735    * @param proc the new procedure to execute.
736    * @return the procedure id, that can be used to monitor the operation
737    */
738   public long submitProcedure(final Procedure proc) {
739     return submitProcedure(proc, null);
740   }
741 
742   /**
743    * Add a new root-procedure to the executor.
744    * @param proc the new procedure to execute.
745    * @param nonceKey the registered unique identifier for this operation from the client or process.
746    * @return the procedure id, that can be used to monitor the operation
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     // Initialize the procedure
766     proc.setNonceKey(nonceKey);
767     proc.setProcId(currentProcId.longValue());
768 
769     // Commit the transaction
770     store.insert(proc, null);
771     if (LOG.isDebugEnabled()) {
772       LOG.debug("Procedure " + proc + " added to the store.");
773     }
774 
775     // Create the rollback stack for the procedure
776     RootProcedureState stack = new RootProcedureState();
777     rollbackStack.put(currentProcId, stack);
778 
779     // Submit the new subprocedures
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    * Return true if the procedure is finished.
793    * The state may be "completed successfully" or "failed and rolledback".
794    * Use getResult() to check the state or get the result data.
795    * @param procId the ID of the procedure to check
796    * @return true if the procedure execution is finished, otherwise false.
797    */
798   public boolean isFinished(final long procId) {
799     return completed.containsKey(procId);
800   }
801 
802   /**
803    * Return true if the procedure is started.
804    * @param procId the ID of the procedure to check
805    * @return true if the procedure execution is started, otherwise false.
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    * Mark the specified completed procedure, as ready to remove.
817    * @param procId the ID of the procedure to remove
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     // The CompletedProcedureCleaner will take care of deletion, once the TTL is expired.
830     result.setClientAckTime(EnvironmentEdgeManager.currentTime());
831   }
832 
833   /**
834    * Send an abort notification the specified procedure.
835    * Depending on the procedure implementation the abort can be considered or ignored.
836    * @param procId the procedure to abort
837    * @return true if the procedure exist and has received the abort, otherwise false.
838    */
839   public boolean abort(final long procId) {
840     return abort(procId, true);
841   }
842 
843   /**
844    * Send an abort notification the specified procedure.
845    * Depending on the procedure implementation the abort can be considered or ignored.
846    * @param procId the procedure to abort
847    * @param mayInterruptIfRunning if the proc completed at least one step, should it be aborted?
848    * @return true if the procedure exist and has received the abort, otherwise false.
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    * Check if the user is this procedure's owner
864    * @param procId the target procedure
865    * @param user the user
866    * @return true if the user is the owner of the procedure,
867    *   false otherwise or the owner is unknown.
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       // Procedure either does not exist or has already completed and got cleaned up.
881       // At this time, we cannot check the owner of the procedure
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    * Execution loop (N threads)
901    * while the executor is in a running state,
902    * fetch a procedure from the runnables queue and start the execution.
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       // The 'proc' was ready to run but the root procedure was rolledback
926       executeRollback(proc);
927       return;
928     }
929 
930     RootProcedureState procStack = rollbackStack.get(rootProcId);
931     if (procStack == null) return;
932 
933     do {
934       // Try to acquire the execution
935       if (!procStack.acquire(proc)) {
936         if (procStack.setRollback()) {
937           // we have the 'rollback-lock' we can start rollingback
938           if (!executeRollback(rootProcId, procStack)) {
939             procStack.unsetRollback();
940             runnables.yield(proc);
941           }
942         } else {
943           // if we can't rollback means that some child is still running.
944           // the rollback will be executed after all the children are done.
945           // If the procedure was never executed, remove and mark it as rolledback.
946           if (!proc.wasExecuted()) {
947             if (!executeRollback(proc)) {
948               runnables.yield(proc);
949             }
950           }
951         }
952         break;
953       }
954 
955       // Execute the procedure
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       // allows to kill the executor before something is stored to the wal.
966       // useful to test the procedure recovery.
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         // Finalize the procedure state
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         // got an early wake, maybe a stop?
992         // re-enqueue the task in case was not a stop or just a signal
993         waitingTimeout.add(proc);
994         continue;
995       }
996 
997       // ----------------------------------------------------------------------------
998       // TODO-MAYBE: Should we provide a notification to the store with the
999       // full set of procedures pending and completed to write a compacted
1000       // version of the log (in case is a log)?
1001       // In theory no, procedures are have a short life, so at some point the store
1002       // will have the tracker saying everything is in the last log.
1003       // ----------------------------------------------------------------------------
1004 
1005       // The CompletedProcedureCleaner is a special case, and it acts as a chore.
1006       // instead of bringing the Chore class in, we reuse this timeout thread for
1007       // this special case.
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       // The procedure received an "abort-timeout", call abort() and
1020       // add the procedure back in the queue for rollback.
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    * Execute the rollback of the full procedure stack.
1034    * Once the procedure is rolledback, the root-procedure will be visible as
1035    * finished to user, and the result will be the fatal exception.
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         // can't take a lock on the procedure, add the root-proc back on the
1056         // queue waiting for the lock availability
1057         return false;
1058       }
1059 
1060       boolean abortRollback = !executeRollback(proc);
1061       abortRollback |= !isRunning() || !store.isRunning();
1062 
1063       // If the next procedure is the same to this one
1064       // (e.g. StateMachineProcedure reuse the same instance)
1065       // we can avoid to lock/unlock each step
1066       reuseLock = stackTail > 0 && (subprocStack.get(stackTail - 1) == proc) && !abortRollback;
1067       if (!reuseLock) {
1068         proc.releaseLock(getEnvironment());
1069       }
1070 
1071       // allows to kill the executor before something is stored to the wal.
1072       // useful to test the procedure recovery.
1073       if (abortRollback) {
1074         return false;
1075       }
1076 
1077       subprocStack.remove(stackTail);
1078 
1079       // if the procedure is kind enough to pass the slot to someone else, yield
1080       if (proc.isYieldAfterExecutionStep(getEnvironment())) {
1081         return false;
1082       }
1083     }
1084 
1085     // Finalize the procedure state
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    * Execute the rollback of the procedure step.
1095    * It updates the store with the new state (stack index)
1096    * or will remove completly the procedure in case it is a child.
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       // Catch NullPointerExceptions or similar errors...
1111       LOG.fatal("CODE-BUG: Uncatched runtime exception for procedure: " + proc, e);
1112     }
1113 
1114     // allows to kill the executor before something is stored to the wal.
1115     // useful to test the procedure recovery.
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    * Executes the specified procedure
1139    *  - calls the doExecute() of the procedure
1140    *  - if the procedure execution didn't fail (e.g. invalid user input)
1141    *     - ...and returned subprocedures
1142    *        - the subprocedures are initialized.
1143    *        - the subprocedures are added to the store
1144    *        - the subprocedures are added to the runnable queue
1145    *        - the procedure is now in a WAITING state, waiting for the subprocedures to complete
1146    *     - ...if there are no subprocedure
1147    *        - the procedure completed successfully
1148    *        - if there is a parent (WAITING)
1149    *            - the parent state will be set to RUNNABLE
1150    *  - in case of failure
1151    *    - the store is updated with the new state
1152    *    - the executor (caller of this method) will start the rollback of the procedure
1153    */
1154   private void execProcedure(final RootProcedureState procStack, final Procedure procedure) {
1155     Preconditions.checkArgument(procedure.getState() == ProcedureState.RUNNABLE);
1156 
1157     // Execute the procedure
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         // Catch NullPointerExceptions or similar errors...
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             // quick-shortcut for a state machine like procedure
1188             subprocs = null;
1189             reExecute = true;
1190           } else {
1191             // yield the current procedure, and make the subprocedure runnable
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           // No subtask, so we are done
1225           procedure.setState(ProcedureState.FINISHED);
1226         }
1227       }
1228 
1229       // Add the procedure to the stack
1230       procStack.addRollbackStep(procedure);
1231 
1232       // allows to kill the executor before something is stored to the wal.
1233       // useful to test the procedure recovery.
1234       if (testing != null && testing.shouldKillBeforeStoreUpdate()) {
1235         LOG.debug("TESTING: Kill before store update");
1236         stop();
1237         return;
1238       }
1239 
1240       // Commit the transaction
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       // if the store is not running we are aborting
1254       if (!store.isRunning()) {
1255         return;
1256       }
1257 
1258       // if the procedure is kind enough to pass the slot to someone else, yield
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     // Submit the new subprocedures
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       // If this procedure is the last child awake the parent procedure
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     // NOTE: We don't call Thread.currentThread().interrupt()
1307     // because otherwise all the subsequent calls e.g. Thread.sleep() will throw
1308     // the InterruptedException. If the master is going down, we will be notified
1309     // and the executor/store will be stopped.
1310     // (The interrupted procedure will be retried on the next run)
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     // call the procedure completion cleanup handler
1375     try {
1376       proc.completionCleanup(getEnvironment());
1377     } catch (Throwable e) {
1378       // Catch NullPointerExceptions or similar errors...
1379       LOG.error("CODE-BUG: uncatched runtime exception for procedure: " + proc, e);
1380     }
1381 
1382     // update the executor internal state maps
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     // call the runnableSet completion cleanup handler
1393     try {
1394       runnables.completionCleanup(proc);
1395     } catch (Throwable e) {
1396       // Catch NullPointerExceptions or similar errors...
1397       LOG.error("CODE-BUG: uncatched runtime exception for runnableSet: " + runnables, e);
1398     }
1399 
1400     // Notify the listeners
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 }