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.store.wal;
20  
21  import java.io.IOException;
22  
23  import org.apache.commons.logging.Log;
24  import org.apache.commons.logging.LogFactory;
25  import org.apache.hadoop.fs.FSDataInputStream;
26  import org.apache.hadoop.hbase.classification.InterfaceAudience;
27  import org.apache.hadoop.hbase.classification.InterfaceStability;
28  import org.apache.hadoop.hbase.ProcedureInfo;
29  import org.apache.hadoop.hbase.procedure2.Procedure;
30  import org.apache.hadoop.hbase.procedure2.store.ProcedureStoreTracker;
31  import org.apache.hadoop.hbase.procedure2.store.ProcedureStore.ProcedureIterator;
32  import org.apache.hadoop.hbase.protobuf.generated.ProcedureProtos;
33  import org.apache.hadoop.hbase.protobuf.generated.ProcedureProtos.ProcedureWALEntry;
34  
35  /**
36   * Helper class that loads the procedures stored in a WAL
37   */
38  @InterfaceAudience.Private
39  @InterfaceStability.Evolving
40  public class ProcedureWALFormatReader {
41    private static final Log LOG = LogFactory.getLog(ProcedureWALFormatReader.class);
42  
43    // ==============================================================================================
44    //  We read the WALs in reverse order. from the newest to the oldest.
45    //  We have different entry types:
46    //   - INIT: Procedure submitted by the user (also known as 'root procedure')
47    //   - INSERT: Children added to the procedure <parentId>:[<childId>, ...]
48    //   - UPDATE: The specified procedure was updated
49    //   - DELETE: The procedure was removed (completed/rolledback and result TTL expired)
50    //
51    // In the WAL we can find multiple times the same procedure as UPDATE or INSERT.
52    // We read the WAL from top to bottom, so every time we find an entry of the
53    // same procedure, that will be the "latest" update.
54    //
55    // We keep two in-memory maps:
56    //  - localProcedureMap: is the map containing the entries in the WAL we are processing
57    //  - procedureMap: is the map containing all the procedures we found up to the WAL in process.
58    // localProcedureMap is merged with the procedureMap once we reach the WAL EOF.
59    //
60    // Since we are reading the WALs in reverse order (newest to oldest),
61    // if we find an entry related to a procedure we already have in 'procedureMap' we can discard it.
62    //
63    // The WAL is append-only so the last procedure in the WAL is the one that
64    // was in execution at the time we crashed/closed the server.
65    // given that, the procedure replay order can be inferred by the WAL order.
66    //
67    // Example:
68    //    WAL-2: [A, B, A, C, D]
69    //    WAL-1: [F, G, A, F, B]
70    //    Replay-Order: [D, C, A, B, F, G]
71    //
72    // The "localProcedureMap" keeps a "replayOrder" list. Every time we add the
73    // record to the map that record is moved to the head of the "replayOrder" list.
74    // Using the example above:
75    //    WAL-2 localProcedureMap.replayOrder is [D, C, A, B]
76    //    WAL-1 localProcedureMap.replayOrder is [F, G]
77    //
78    // each time we reach the WAL-EOF, the "replayOrder" list is merged/appended in 'procedureMap'
79    // so using the example above we end up with: [D, C, A, B] + [F, G] as replay order.
80    //
81    //  Fast Start: INIT/INSERT record and StackIDs
82    // ---------------------------------------------
83    // We have two special record, INIT and INSERT that tracks the first time
84    // the procedure was added to the WAL. We can use that information to be able
85    // to start procedures before reaching the end of the WAL, or before reading all the WALs.
86    // but in some cases the WAL with that record can be already gone.
87    // In alternative we can use the stackIds on each procedure,
88    // to identify when a procedure is ready to start.
89    // If there are gaps in the sum of the stackIds we need to read more WALs.
90    //
91    // Example (all procs child of A):
92    //   WAL-2: [A, B]                   A stackIds = [0, 4], B stackIds = [1, 5]
93    //   WAL-1: [A, B, C, D]
94    //
95    // In the case above we need to read one more WAL to be able to consider
96    // the root procedure A and all children as ready.
97    // ==============================================================================================
98    private final WalProcedureMap localProcedureMap = new WalProcedureMap(1024);
99    private final WalProcedureMap procedureMap = new WalProcedureMap(1024);
100 
101   //private long compactionLogId;
102   private long maxProcId = 0;
103 
104   private final ProcedureStoreTracker tracker;
105   private final boolean hasFastStartSupport;
106 
107   public ProcedureWALFormatReader(final ProcedureStoreTracker tracker) {
108     this.tracker = tracker;
109     // we support fast-start only if we have a clean shutdown.
110     this.hasFastStartSupport = !tracker.isEmpty();
111   }
112 
113   public void read(ProcedureWALFile log, ProcedureWALFormat.Loader loader) throws IOException {
114     FSDataInputStream stream = log.getStream();
115     try {
116       boolean hasMore = true;
117       while (hasMore) {
118         ProcedureWALEntry entry = ProcedureWALFormat.readEntry(stream);
119         if (entry == null) {
120           LOG.warn("nothing left to decode. exiting with missing EOF");
121           hasMore = false;
122           break;
123         }
124         switch (entry.getType()) {
125           case INIT:
126             readInitEntry(entry);
127             break;
128           case INSERT:
129             readInsertEntry(entry);
130             break;
131           case UPDATE:
132           case COMPACT:
133             readUpdateEntry(entry);
134             break;
135           case DELETE:
136             readDeleteEntry(entry);
137             break;
138           case EOF:
139             hasMore = false;
140             break;
141           default:
142             throw new CorruptedWALProcedureStoreException("Invalid entry: " + entry);
143         }
144       }
145     } catch (IOException e) {
146       LOG.error("got an exception while reading the procedure WAL: " + log, e);
147       loader.markCorruptedWAL(log, e);
148     }
149 
150     if (!localProcedureMap.isEmpty()) {
151       log.setProcIds(localProcedureMap.getMinProcId(), localProcedureMap.getMaxProcId());
152       procedureMap.mergeTail(localProcedureMap);
153       //if (hasFastStartSupport) {
154         // TODO: Some procedure may be already runnables (see readInitEntry())
155         //       (we can also check the "update map" in the log trackers)
156         // --------------------------------------------------
157         //EntryIterator iter = procedureMap.fetchReady();
158         //if (iter != null) loader.load(iter);
159         // --------------------------------------------------
160       //}
161     }
162   }
163 
164   public void finalize(ProcedureWALFormat.Loader loader) throws IOException {
165     // notify the loader about the max proc ID
166     loader.setMaxProcId(maxProcId);
167 
168     // fetch the procedure ready to run.
169     ProcedureIterator procIter = procedureMap.fetchReady();
170     if (procIter != null) loader.load(procIter);
171 
172     // remaining procedures have missing link or dependencies
173     // consider them as corrupted, manual fix is probably required.
174     procIter = procedureMap.fetchAll();
175     if (procIter != null) loader.handleCorrupted(procIter);
176   }
177 
178   private void loadProcedure(final ProcedureWALEntry entry, final ProcedureProtos.Procedure proc) {
179     maxProcId = Math.max(maxProcId, proc.getProcId());
180     if (isRequired(proc.getProcId())) {
181       if (LOG.isTraceEnabled()) {
182         LOG.trace("read " + entry.getType() + " entry " + proc.getProcId());
183       }
184       localProcedureMap.add(proc);
185       tracker.setDeleted(proc.getProcId(), false);
186     }
187   }
188 
189   private void readInitEntry(final ProcedureWALEntry entry)
190       throws IOException {
191     assert entry.getProcedureCount() == 1 : "Expected only one procedure";
192     loadProcedure(entry, entry.getProcedure(0));
193   }
194 
195   private void readInsertEntry(final ProcedureWALEntry entry) throws IOException {
196     assert entry.getProcedureCount() >= 1 : "Expected one or more procedures";
197     loadProcedure(entry, entry.getProcedure(0));
198     for (int i = 1; i < entry.getProcedureCount(); ++i) {
199       loadProcedure(entry, entry.getProcedure(i));
200     }
201   }
202 
203   private void readUpdateEntry(final ProcedureWALEntry entry) throws IOException {
204     assert entry.getProcedureCount() == 1 : "Expected only one procedure";
205     loadProcedure(entry, entry.getProcedure(0));
206   }
207 
208   private void readDeleteEntry(final ProcedureWALEntry entry) throws IOException {
209     assert entry.getProcedureCount() == 0 : "Expected no procedures";
210     assert entry.hasProcId() : "expected ProcID";
211     if (LOG.isTraceEnabled()) {
212       LOG.trace("read delete entry " + entry.getProcId());
213     }
214     maxProcId = Math.max(maxProcId, entry.getProcId());
215     localProcedureMap.remove(entry.getProcId());
216     assert !procedureMap.contains(entry.getProcId());
217     tracker.setDeleted(entry.getProcId(), true);
218   }
219 
220   private boolean isDeleted(final long procId) {
221     return tracker.isDeleted(procId) == ProcedureStoreTracker.DeleteState.YES;
222   }
223 
224   private boolean isRequired(final long procId) {
225     return !isDeleted(procId) && !procedureMap.contains(procId);
226   }
227 
228   // ==========================================================================
229   //  We keep an in-memory map of the procedures sorted by replay order.
230   //  (see the details in the beginning of the file)
231   //                      _______________________________________________
232   //      procedureMap = | A |   | E |   | C |   |   |   |   | G |   |   |
233   //                       D               B
234   //      replayOrderHead = C <-> B <-> E <-> D <-> A <-> G
235   //
236   //  We also have a lazy grouping by "root procedure", and a list of
237   //  unlinked procedure. If after reading all the WALs we have unlinked
238   //  procedures it means that we had a missing WAL or a corruption.
239   //      rootHead = A <-> D <-> G
240   //                 B     E
241   //                 C
242   //      unlinkFromLinkList = None
243   // ==========================================================================
244   private static class Entry {
245     // hash-table next
246     protected Entry hashNext;
247     // child head
248     protected Entry childHead;
249     // double-link for rootHead or childHead
250     protected Entry linkNext;
251     protected Entry linkPrev;
252     // replay double-linked-list
253     protected Entry replayNext;
254     protected Entry replayPrev;
255     // procedure-infos
256     protected Procedure procedure;
257     protected ProcedureProtos.Procedure proto;
258     protected boolean ready = false;
259 
260     public Entry(Entry hashNext) { this.hashNext = hashNext; }
261 
262     public long getProcId() { return proto.getProcId(); }
263     public long getParentId() { return proto.getParentId(); }
264     public boolean hasParent() { return proto.hasParentId(); }
265     public boolean isReady() { return ready; }
266 
267     public boolean isCompleted() {
268       if (!hasParent()) {
269         switch (proto.getState()) {
270           case ROLLEDBACK:
271             return true;
272           case FINISHED:
273             return !proto.hasException();
274           default:
275             break;
276         }
277       }
278       return false;
279     }
280 
281     public Procedure convert() throws IOException {
282       if (procedure == null) {
283         procedure = Procedure.convert(proto);
284       }
285       return procedure;
286     }
287 
288     public ProcedureInfo convertToInfo() {
289       return ProcedureInfo.convert(proto);
290     }
291 
292     @Override
293     public String toString() {
294       return "Entry(" + getProcId() + ", parentId=" + getParentId() + ")";
295     }
296   }
297 
298   private static class EntryIterator implements ProcedureIterator {
299     private final Entry replayHead;
300     private Entry current;
301 
302     public EntryIterator(Entry replayHead) {
303       this.replayHead = replayHead;
304       this.current = replayHead;
305     }
306 
307     @Override
308     public void reset() {
309       this.current = replayHead;
310     }
311 
312     @Override
313     public boolean hasNext() {
314       return current != null;
315     }
316 
317     @Override
318     public boolean isNextCompleted() {
319       return current != null && current.isCompleted();
320     }
321 
322     @Override
323     public void skipNext() {
324       current = current.replayNext;
325     }
326 
327     @Override
328     public Procedure nextAsProcedure() throws IOException {
329       try {
330         return current.convert();
331       } finally {
332         current = current.replayNext;
333       }
334     }
335 
336     @Override
337     public ProcedureInfo nextAsProcedureInfo() {
338       try {
339         return current.convertToInfo();
340       } finally {
341         current = current.replayNext;
342       }
343     }
344   }
345 
346   private static class WalProcedureMap {
347     // procedure hash table
348     private Entry[] procedureMap;
349 
350     // replay-order double-linked-list
351     private Entry replayOrderHead;
352     private Entry replayOrderTail;
353 
354     // root linked-list
355     private Entry rootHead;
356 
357     // pending unlinked children (root not present yet)
358     private Entry childUnlinkedHead;
359 
360     // Track ProcId range
361     private long minProcId = Long.MAX_VALUE;
362     private long maxProcId = Long.MIN_VALUE;
363 
364     public WalProcedureMap(int size) {
365       procedureMap = new Entry[size];
366       replayOrderHead = null;
367       replayOrderTail = null;
368       rootHead = null;
369       childUnlinkedHead = null;
370     }
371 
372     public void add(ProcedureProtos.Procedure procProto) {
373       trackProcIds(procProto.getProcId());
374       Entry entry = addToMap(procProto.getProcId(), procProto.hasParentId());
375       boolean isNew = entry.proto == null;
376       entry.proto = procProto;
377       addToReplayList(entry);
378 
379       if (isNew) {
380         if (procProto.hasParentId()) {
381           childUnlinkedHead = addToLinkList(entry, childUnlinkedHead);
382         } else {
383           rootHead = addToLinkList(entry, rootHead);
384         }
385       }
386     }
387 
388     public boolean remove(long procId) {
389       trackProcIds(procId);
390       Entry entry = removeFromMap(procId);
391       if (entry != null) {
392         unlinkFromReplayList(entry);
393         unlinkFromLinkList(entry);
394         return true;
395       }
396       return false;
397     }
398 
399     private void trackProcIds(long procId) {
400       minProcId = Math.min(minProcId, procId);
401       maxProcId = Math.max(maxProcId, procId);
402     }
403 
404     public long getMinProcId() {
405       return minProcId;
406     }
407 
408     public long getMaxProcId() {
409       return maxProcId;
410     }
411 
412     public boolean contains(long procId) {
413       return getProcedure(procId) != null;
414     }
415 
416     public boolean isEmpty() {
417       return replayOrderHead == null;
418     }
419 
420     public void clear() {
421       for (int i = 0; i < procedureMap.length; ++i) {
422         procedureMap[i] = null;
423       }
424       replayOrderHead = null;
425       replayOrderTail = null;
426       rootHead = null;
427       childUnlinkedHead = null;
428       minProcId = Long.MAX_VALUE;
429       maxProcId = Long.MIN_VALUE;
430     }
431 
432     /*
433      * Merges two WalProcedureMap,
434      * the target is the "global" map, the source is the "local" map.
435      *  - The entries in the hashtables are guaranteed to be unique.
436      *    On replay we don't load procedures that already exist in the "global"
437      *    map (the one we are merging the "local" in to).
438      *  - The replayOrderList of the "local" nao will be appended to the "global"
439      *    map replay list.
440      *  - The "local" map will be cleared at the end of the operation.
441      */
442     public void mergeTail(WalProcedureMap other) {
443       for (Entry p = other.replayOrderHead; p != null; p = p.replayNext) {
444         int slotIndex = getMapSlot(p.getProcId());
445         p.hashNext = procedureMap[slotIndex];
446         procedureMap[slotIndex] = p;
447       }
448 
449       if (replayOrderHead == null) {
450         replayOrderHead = other.replayOrderHead;
451         replayOrderTail = other.replayOrderTail;
452         rootHead = other.rootHead;
453         childUnlinkedHead = other.childUnlinkedHead;
454       } else {
455         // append replay list
456         assert replayOrderTail.replayNext == null;
457         assert other.replayOrderHead.replayPrev == null;
458         replayOrderTail.replayNext = other.replayOrderHead;
459         other.replayOrderHead.replayPrev = replayOrderTail;
460         replayOrderTail = other.replayOrderTail;
461 
462         // merge rootHead
463         if (rootHead == null) {
464           rootHead = other.rootHead;
465         } else if (other.rootHead != null) {
466           Entry otherTail = findLinkListTail(other.rootHead);
467           otherTail.linkNext = rootHead;
468           rootHead.linkPrev = otherTail;
469           rootHead = other.rootHead;
470         }
471 
472         // merge childUnlinkedHead
473         if (childUnlinkedHead == null) {
474           childUnlinkedHead = other.childUnlinkedHead;
475         } else if (other.childUnlinkedHead != null) {
476           Entry otherTail = findLinkListTail(other.childUnlinkedHead);
477           otherTail.linkNext = childUnlinkedHead;
478           childUnlinkedHead.linkPrev = otherTail;
479           childUnlinkedHead = other.childUnlinkedHead;
480         }
481       }
482 
483       other.clear();
484     }
485 
486     /*
487      * Returns an EntryIterator with the list of procedures ready
488      * to be added to the executor.
489      * A Procedure is ready if its children and parent are ready.
490      */
491     public EntryIterator fetchReady() {
492       buildGraph();
493 
494       Entry readyHead = null;
495       Entry readyTail = null;
496       Entry p = replayOrderHead;
497       while (p != null) {
498         Entry next = p.replayNext;
499         if (p.isReady()) {
500           unlinkFromReplayList(p);
501           if (readyTail != null) {
502             readyTail.replayNext = p;
503             p.replayPrev = readyTail;
504           } else {
505             p.replayPrev = null;
506             readyHead = p;
507           }
508           readyTail = p;
509           p.replayNext = null;
510         }
511         p = next;
512       }
513       // we need the hash-table lookups for parents, so this must be done
514       // out of the loop where we check isReadyToRun()
515       for (p = readyHead; p != null; p = p.replayNext) {
516         removeFromMap(p.getProcId());
517         unlinkFromLinkList(p);
518       }
519       return readyHead != null ? new EntryIterator(readyHead) : null;
520     }
521 
522     /*
523      * Drain this map and return all procedures in it.
524      */
525     public EntryIterator fetchAll() {
526       Entry head = replayOrderHead;
527       for (Entry p = head; p != null; p = p.replayNext) {
528         removeFromMap(p.getProcId());
529       }
530       for (int i = 0; i < procedureMap.length; ++i) {
531         assert procedureMap[i] == null : "map not empty i=" + i;
532       }
533       replayOrderHead = null;
534       replayOrderTail = null;
535       childUnlinkedHead = null;
536       rootHead = null;
537       return head != null ? new EntryIterator(head) : null;
538     }
539 
540     private void buildGraph() {
541       Entry p = childUnlinkedHead;
542       while (p != null) {
543         Entry next = p.linkNext;
544         Entry rootProc = getRootProcedure(p);
545         if (rootProc != null) {
546           rootProc.childHead = addToLinkList(p, rootProc.childHead);
547         }
548         p = next;
549       }
550 
551       for (p = rootHead; p != null; p = p.linkNext) {
552         checkReadyToRun(p);
553       }
554     }
555 
556     private Entry getRootProcedure(Entry entry) {
557       while (entry != null && entry.hasParent()) {
558         entry = getProcedure(entry.getParentId());
559       }
560       return entry;
561     }
562 
563     /*
564      * (see the comprehensive explaination in the beginning of the file)
565      * A Procedure is ready when parent and children are ready.
566      * "ready" means that we all the information that we need in-memory.
567      *
568      * Example-1:
569      * We have two WALs, we start reading fronm the newest (wal-2)
570      *    wal-2 | C B |
571      *    wal-1 | A B C |
572      *
573      * If C and B don't depend on A (A is not the parent), we can start them
574      * before reading wal-1. If B is the only one with parent A we can start C
575      * and read one more WAL before being able to start B.
576      *
577      * How do we know with the only information in B that we are not ready.
578      *  - easy case, the parent is missing from the global map
579      *  - more complex case we look at the Stack IDs
580      *
581      * The Stack-IDs are added to the procedure order as incremental index
582      * tracking how many times that procedure was executed, which is equivalent
583      * at the number of times we wrote the procedure to the WAL.
584      * In the example above:
585      *   wal-2: B has stackId = [1, 2]
586      *   wal-1: B has stackId = [1]
587      *   wal-1: A has stackId = [0]
588      *
589      * Since we know that the Stack-IDs are incremental for a Procedure,
590      * we notice that there is a gap in the stackIds of B, so something was
591      * executed before.
592      * To identify when a Procedure is ready we do the sum of the stackIds of
593      * the procedure and the parent. if the stackIdSum is equals to the
594      * sum of {1..maxStackId} then everything we need is avaiable.
595      *
596      * Example-2
597      *    wal-2 | A |              A stackIds = [0, 2]
598      *    wal-1 | A B |            B stackIds = [1]
599      *
600      * There is a gap between A stackIds so something was executed in between.
601      */
602     private boolean checkReadyToRun(Entry rootEntry) {
603       int stackIdSum = 0;
604       int maxStackId = 0;
605       for (int i = 0; i < rootEntry.proto.getStackIdCount(); ++i) {
606         int stackId = 1 + rootEntry.proto.getStackId(i);
607         maxStackId  = Math.max(maxStackId, stackId);
608         stackIdSum += stackId;
609       }
610 
611       for (Entry p = rootEntry.childHead; p != null; p = p.linkNext) {
612         for (int i = 0; i < p.proto.getStackIdCount(); ++i) {
613           int stackId = 1 + p.proto.getStackId(i);
614           maxStackId  = Math.max(maxStackId, stackId);
615           stackIdSum += stackId;
616         }
617       }
618       final int cmpStackIdSum = (maxStackId * (maxStackId + 1) / 2);
619       if (cmpStackIdSum == stackIdSum) {
620         rootEntry.ready = true;
621         for (Entry p = rootEntry.childHead; p != null; p = p.linkNext) {
622           p.ready = true;
623         }
624         return true;
625       }
626       return false;
627     }
628 
629     private void unlinkFromReplayList(Entry entry) {
630       if (replayOrderHead == entry) {
631         replayOrderHead = entry.replayNext;
632       }
633       if (replayOrderTail == entry) {
634         replayOrderTail = entry.replayPrev;
635       }
636       if (entry.replayPrev != null) {
637         entry.replayPrev.replayNext = entry.replayNext;
638       }
639       if (entry.replayNext != null) {
640         entry.replayNext.replayPrev = entry.replayPrev;
641       }
642     }
643 
644     private void addToReplayList(final Entry entry) {
645       unlinkFromReplayList(entry);
646       entry.replayNext = replayOrderHead;
647       entry.replayPrev = null;
648       if (replayOrderHead != null) {
649         replayOrderHead.replayPrev = entry;
650       } else {
651         replayOrderTail = entry;
652       }
653       replayOrderHead = entry;
654     }
655 
656     private void unlinkFromLinkList(Entry entry) {
657       if (entry == rootHead) {
658         rootHead = entry.linkNext;
659       } else if (entry == childUnlinkedHead) {
660         childUnlinkedHead = entry.linkNext;
661       }
662       if (entry.linkPrev != null) {
663         entry.linkPrev.linkNext = entry.linkNext;
664       }
665       if (entry.linkNext != null) {
666         entry.linkNext.linkPrev = entry.linkPrev;
667       }
668     }
669 
670     private Entry addToLinkList(Entry entry, Entry linkHead) {
671       unlinkFromLinkList(entry);
672       entry.linkNext = linkHead;
673       entry.linkPrev = null;
674       if (linkHead != null) {
675         linkHead.linkPrev = entry;
676       }
677       return entry;
678     }
679 
680     private Entry findLinkListTail(Entry linkHead) {
681       Entry tail = linkHead;
682       while (tail.linkNext != null) {
683         tail = tail.linkNext;
684       }
685       return tail;
686     }
687 
688     private Entry addToMap(final long procId, final boolean hasParent) {
689       int slotIndex = getMapSlot(procId);
690       Entry entry = getProcedure(slotIndex, procId);
691       if (entry != null) return entry;
692 
693       entry = new Entry(procedureMap[slotIndex]);
694       procedureMap[slotIndex] = entry;
695       return entry;
696     }
697 
698     private Entry removeFromMap(final long procId) {
699       int slotIndex = getMapSlot(procId);
700       Entry prev = null;
701       Entry entry = procedureMap[slotIndex];
702       while (entry != null) {
703         if (procId == entry.getProcId()) {
704           if (prev != null) {
705             prev.hashNext = entry.hashNext;
706           } else {
707             procedureMap[slotIndex] = entry.hashNext;
708           }
709           entry.hashNext = null;
710           return entry;
711         }
712         prev = entry;
713         entry = entry.hashNext;
714       }
715       return null;
716     }
717 
718     private Entry getProcedure(final long procId) {
719       return getProcedure(getMapSlot(procId), procId);
720     }
721 
722     private Entry getProcedure(final int slotIndex, final long procId) {
723       Entry entry = procedureMap[slotIndex];
724       while (entry != null) {
725         if (procId == entry.getProcId()) {
726           return entry;
727         }
728         entry = entry.hashNext;
729       }
730       return null;
731     }
732 
733     private int getMapSlot(final long procId) {
734       return (int)(Procedure.getProcIdHashCode(procId) % procedureMap.length);
735     }
736   }
737 }