@Override
 public <KOut, VOut> Map<KOut, VOut> reduce(ReduceCommand<KOut, VOut> reduceCommand)
     throws InterruptedException {
   Cache<?, ?> cache = cacheManager.getCache(reduceCommand.getCacheName());
   Set<KOut> keys = reduceCommand.getKeys();
   String taskId = reduceCommand.getTaskId();
   Reducer<KOut, VOut> reducer = reduceCommand.getReducer();
   boolean useIntermediateKeys = reduceCommand.isEmitCompositeIntermediateKeys();
   boolean noInputKeys = keys == null || keys.isEmpty();
   Cache<Object, List<VOut>> tmpCache = cacheManager.getCache(reduceCommand.getCacheName());
   Map<KOut, VOut> result = new HashMap<KOut, VOut>();
   if (noInputKeys) {
     // illegal state, raise exception
     throw new IllegalStateException(
         "Reduce phase of MapReduceTask "
             + taskId
             + " on node "
             + cdl.getAddress()
             + " executed with empty input keys");
   } else {
     // first hook into lifecycle
     MapReduceTaskLifecycleService taskLifecycleService =
         MapReduceTaskLifecycleService.getInstance();
     log.tracef("For m/r task %s invoking %s at %s", taskId, reduceCommand, cdl.getAddress());
     int interruptCount = 0;
     long start = log.isTraceEnabled() ? timeService.time() : 0;
     try {
       taskLifecycleService.onPreExecute(reducer, cache);
       for (KOut key : keys) {
         interruptCount++;
         if (checkInterrupt(interruptCount++) && Thread.currentThread().isInterrupted())
           throw new InterruptedException();
         // load result value from map phase
         List<VOut> value;
         if (useIntermediateKeys) {
           value = tmpCache.get(new IntermediateCompositeKey<KOut>(taskId, key));
         } else {
           value = tmpCache.get(key);
         }
         // and reduce it
         VOut reduced = reducer.reduce(key, value.iterator());
         result.put(key, reduced);
         log.tracef(
             "For m/r task %s reduced %s to %s at %s ", taskId, key, reduced, cdl.getAddress());
       }
     } finally {
       if (log.isTraceEnabled()) {
         log.tracef(
             "Reduce for task %s took %s milliseconds",
             reduceCommand.getTaskId(), timeService.timeDuration(start, TimeUnit.MILLISECONDS));
       }
       taskLifecycleService.onPostExecute(reducer);
     }
   }
   return result;
 }