View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core;
18  
19  import static org.apache.logging.log4j.core.util.ShutdownCallbackRegistry.SHUTDOWN_HOOK_MARKER;
20  
21  import java.beans.PropertyChangeEvent;
22  import java.beans.PropertyChangeListener;
23  import java.io.File;
24  import java.net.URI;
25  import java.util.Collection;
26  import java.util.Objects;
27  import java.util.concurrent.ConcurrentMap;
28  import java.util.concurrent.CopyOnWriteArrayList;
29  import java.util.concurrent.ExecutorService;
30  import java.util.concurrent.Executors;
31  import java.util.concurrent.Future;
32  import java.util.concurrent.RejectedExecutionException;
33  import java.util.concurrent.TimeUnit;
34  import java.util.concurrent.locks.Lock;
35  import java.util.concurrent.locks.ReentrantLock;
36  
37  import org.apache.logging.log4j.LogManager;
38  import org.apache.logging.log4j.core.config.Configuration;
39  import org.apache.logging.log4j.core.config.ConfigurationFactory;
40  import org.apache.logging.log4j.core.config.ConfigurationListener;
41  import org.apache.logging.log4j.core.config.ConfigurationSource; // SUPPRESS CHECKSTYLE
42  import org.apache.logging.log4j.core.config.DefaultConfiguration;
43  import org.apache.logging.log4j.core.config.NullConfiguration;
44  import org.apache.logging.log4j.core.config.Reconfigurable;
45  import org.apache.logging.log4j.core.impl.Log4jLogEvent;
46  import org.apache.logging.log4j.core.jmx.Server;
47  import org.apache.logging.log4j.core.util.Cancellable;
48  import org.apache.logging.log4j.core.util.ExecutorServices;
49  import org.apache.logging.log4j.core.util.Log4jThreadFactory;
50  import org.apache.logging.log4j.core.util.NetUtils;
51  import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
52  import org.apache.logging.log4j.message.MessageFactory;
53  import org.apache.logging.log4j.spi.AbstractLogger;
54  import org.apache.logging.log4j.spi.LoggerContextFactory;
55  import org.apache.logging.log4j.spi.LoggerRegistry;
56  import org.apache.logging.log4j.spi.Terminable;
57  import org.apache.logging.log4j.util.PropertiesUtil;
58  
59  /**
60   * The LoggerContext is the anchor for the logging system. It maintains a list of all the loggers requested by
61   * applications and a reference to the Configuration. The Configuration will contain the configured loggers, appenders,
62   * filters, etc and will be atomically updated whenever a reconfigure occurs.
63   */
64  public class LoggerContext extends AbstractLifeCycle
65          implements org.apache.logging.log4j.spi.LoggerContext, AutoCloseable, Terminable, ConfigurationListener {
66  
67      /**
68       * Property name of the property change event fired if the configuration is changed.
69       */
70      public static final String PROPERTY_CONFIG = "config";
71  
72      private static final Configuration NULL_CONFIGURATION = new NullConfiguration();
73  
74      private final LoggerRegistry<Logger> loggerRegistry = new LoggerRegistry<>();
75      private final CopyOnWriteArrayList<PropertyChangeListener> propertyChangeListeners = new CopyOnWriteArrayList<>();
76  
77      /**
78       * The Configuration is volatile to guarantee that initialization of the Configuration has completed before the
79       * reference is updated.
80       */
81      private volatile Configuration configuration = new DefaultConfiguration();
82      private ExecutorService executorService;
83      private ExecutorService executorServiceDeamons;
84      private Object externalContext;
85      private String contextName;
86      private volatile URI configLocation;
87      private Cancellable shutdownCallback;
88  
89      private final Lock configLock = new ReentrantLock();
90  
91      /**
92       * Constructor taking only a name.
93       *
94       * @param name The context name.
95       */
96      public LoggerContext(final String name) {
97          this(name, null, (URI) null);
98      }
99  
100     /**
101      * Constructor taking a name and a reference to an external context.
102      *
103      * @param name The context name.
104      * @param externalContext The external context.
105      */
106     public LoggerContext(final String name, final Object externalContext) {
107         this(name, externalContext, (URI) null);
108     }
109 
110     /**
111      * Constructor taking a name, external context and a configuration URI.
112      *
113      * @param name The context name.
114      * @param externalContext The external context.
115      * @param configLocn The location of the configuration as a URI.
116      */
117     public LoggerContext(final String name, final Object externalContext, final URI configLocn) {
118         this.contextName = name;
119         this.externalContext = externalContext;
120         this.configLocation = configLocn;
121     }
122 
123     /**
124      * Constructor taking a name external context and a configuration location String. The location must be resolvable
125      * to a File.
126      *
127      * @param name The configuration location.
128      * @param externalContext The external context.
129      * @param configLocn The configuration location.
130      */
131     public LoggerContext(final String name, final Object externalContext, final String configLocn) {
132         this.contextName = name;
133         this.externalContext = externalContext;
134         if (configLocn != null) {
135             URI uri;
136             try {
137                 uri = new File(configLocn).toURI();
138             } catch (final Exception ex) {
139                 uri = null;
140             }
141             configLocation = uri;
142         } else {
143             configLocation = null;
144         }
145     }
146 
147     /**
148      * Returns the current LoggerContext.
149      * <p>
150      * Avoids the type cast for:
151      * </p>
152      *
153      * <pre>
154      * (LoggerContext) LogManager.getContext();
155      * </pre>
156      *
157      * <p>
158      * WARNING - The LoggerContext returned by this method may not be the LoggerContext used to create a Logger for the
159      * calling class.
160      * </p>
161      *
162      * @return The current LoggerContext.
163      * @see LogManager#getContext()
164      */
165     public static LoggerContext getContext() {
166         return (LoggerContext) LogManager.getContext();
167     }
168 
169     /**
170      * Returns a LoggerContext.
171      * <p>
172      * Avoids the type cast for:
173      * </p>
174      *
175      * <pre>
176      * (LoggerContext) LogManager.getContext(currentContext);
177      * </pre>
178      *
179      * @param currentContext if false the LoggerContext appropriate for the caller of this method is returned. For
180      *            example, in a web application if the caller is a class in WEB-INF/lib then one LoggerContext may be
181      *            returned and if the caller is a class in the container's classpath then a different LoggerContext may
182      *            be returned. If true then only a single LoggerContext will be returned.
183      * @return a LoggerContext.
184      * @see LogManager#getContext(boolean)
185      */
186     public static LoggerContext getContext(final boolean currentContext) {
187         return (LoggerContext) LogManager.getContext(currentContext);
188     }
189 
190     /**
191      * Returns a LoggerContext.
192      * <p>
193      * Avoids the type cast for:
194      * </p>
195      *
196      * <pre>
197      * (LoggerContext) LogManager.getContext(loader, currentContext, configLocation);
198      * </pre>
199      *
200      * @param loader The ClassLoader for the context. If null the context will attempt to determine the appropriate
201      *            ClassLoader.
202      * @param currentContext if false the LoggerContext appropriate for the caller of this method is returned. For
203      *            example, in a web application if the caller is a class in WEB-INF/lib then one LoggerContext may be
204      *            returned and if the caller is a class in the container's classpath then a different LoggerContext may
205      *            be returned. If true then only a single LoggerContext will be returned.
206      * @param configLocation The URI for the configuration to use.
207      * @return a LoggerContext.
208      * @see LogManager#getContext(ClassLoader, boolean, URI)
209      */
210     public static LoggerContext getContext(final ClassLoader loader, final boolean currentContext,
211             final URI configLocation) {
212         return (LoggerContext) LogManager.getContext(loader, currentContext, configLocation);
213     }
214 
215     @Override
216     public void start() {
217         LOGGER.debug("Starting LoggerContext[name={}, {}]...", getName(), this);
218         if (PropertiesUtil.getProperties().getBooleanProperty("log4j.LoggerContext.stacktrace.on.start", false)) {
219             LOGGER.debug("Stack trace to locate invoker",
220                     new Exception("Not a real error, showing stack trace to locate invoker"));
221         }
222         if (configLock.tryLock()) {
223             try {
224                 if (this.isInitialized() || this.isStopped()) {
225                     this.setStarting();
226                     reconfigure();
227                     if (this.configuration.isShutdownHookEnabled()) {
228                         setUpShutdownHook();
229                     }
230                     this.setStarted();
231                 }
232             } finally {
233                 configLock.unlock();
234             }
235         }
236         LOGGER.debug("LoggerContext[name={}, {}] started OK.", getName(), this);
237     }
238 
239     /**
240      * Starts with a specific configuration.
241      *
242      * @param config The new Configuration.
243      */
244     public void start(final Configuration config) {
245         LOGGER.debug("Starting LoggerContext[name={}, {}] with configuration {}...", getName(), this, config);
246         if (configLock.tryLock()) {
247             try {
248                 if (this.isInitialized() || this.isStopped()) {
249                     if (this.configuration.isShutdownHookEnabled()) {
250                         setUpShutdownHook();
251                     }
252                     this.setStarted();
253                 }
254             } finally {
255                 configLock.unlock();
256             }
257         }
258         setConfiguration(config);
259         LOGGER.debug("LoggerContext[name={}, {}] started OK with configuration {}.", getName(), this, config);
260     }
261 
262     private void setUpShutdownHook() {
263         if (shutdownCallback == null) {
264             final LoggerContextFactory factory = LogManager.getFactory();
265             if (factory instanceof ShutdownCallbackRegistry) {
266                 LOGGER.debug(SHUTDOWN_HOOK_MARKER, "Shutdown hook enabled. Registering a new one.");
267                 try {
268                     this.shutdownCallback = ((ShutdownCallbackRegistry) factory).addShutdownCallback(new Runnable() {
269                         @Override
270                         public void run() {
271                             @SuppressWarnings("resource")
272                             final LoggerContext context = LoggerContext.this;
273                             LOGGER.debug(SHUTDOWN_HOOK_MARKER, "Stopping LoggerContext[name={}, {}]",
274                                     context.getName(), context);
275                             context.stop();
276                         }
277 
278                         @Override
279                         public String toString() {
280                             return "Shutdown callback for LoggerContext[name=" + LoggerContext.this.getName() + ']';
281                         }
282                     });
283                 } catch (final IllegalStateException e) {
284                     throw new IllegalStateException(
285                             "Unable to register Log4j shutdown hook because JVM is shutting down.", e);
286                 } catch (final SecurityException e) {
287                     LOGGER.error(SHUTDOWN_HOOK_MARKER, "Unable to register shutdown hook due to security restrictions",
288                             e);
289                 }
290             }
291         }
292     }
293 
294     @Override
295     public void close() {
296         stop();
297     }
298 
299     @Override
300     public void terminate() {
301         stop();
302     }
303 
304     @Override
305     public boolean stop(final long timeout, final TimeUnit timeUnit) {
306         LOGGER.debug("Stopping LoggerContext[name={}, {}]...", getName(), this);
307         configLock.lock();
308         final boolean shutdownEs;
309         final boolean shutdownEsd;
310         try {
311             if (this.isStopped()) {
312                 return true;
313             }
314 
315             this.setStopping();
316             try {
317                 Server.unregisterLoggerContext(getName()); // LOG4J2-406, LOG4J2-500
318             } catch (final LinkageError | Exception e) {
319                 // LOG4J2-1506 Hello Android, GAE
320                 LOGGER.error("Unable to unregister MBeans", e);
321             }
322             if (shutdownCallback != null) {
323                 shutdownCallback.cancel();
324                 shutdownCallback = null;
325             }
326             final Configuration prev = configuration;
327             configuration = NULL_CONFIGURATION;
328             updateLoggers();
329             if (prev instanceof LifeCycle2) {
330                 ((LifeCycle2) prev).stop(timeout, timeUnit);
331             } else {
332                 prev.stop();
333             }
334             externalContext = null;
335             LogManager.getFactory().removeContext(this);
336             final String source = "LoggerContext \'" + getName() + "\'";
337             shutdownEs = ExecutorServices.shutdown(executorService, timeout, timeUnit, source);
338             // Do not wait for daemon threads
339             shutdownEsd = ExecutorServices.shutdown(executorServiceDeamons, -1, timeUnit, source);
340         } finally {
341             configLock.unlock();
342             this.setStopped();
343         }
344         LOGGER.debug("Stopped LoggerContext[name={}, {}]...", getName(), this);
345         return shutdownEs && shutdownEsd;
346     }
347 
348     /**
349      * Gets the name.
350      *
351      * @return the name.
352      */
353     public String getName() {
354         return contextName;
355     }
356 
357     /**
358      * Gets the root logger.
359      *
360      * @return the root logger.
361      */
362     public Logger getRootLogger() {
363         return getLogger(LogManager.ROOT_LOGGER_NAME);
364     }
365 
366     /**
367      * Sets the name.
368      *
369      * @param name the new LoggerContext name
370      * @throws NullPointerException if the specified name is {@code null}
371      */
372     public void setName(final String name) {
373     	contextName = Objects.requireNonNull(name);
374     }
375 
376     /**
377      * Sets the external context.
378      *
379      * @param context The external context.
380      */
381     public void setExternalContext(final Object context) {
382         this.externalContext = context;
383     }
384 
385     /**
386      * Returns the external context.
387      *
388      * @return The external context.
389      */
390     @Override
391     public Object getExternalContext() {
392         return this.externalContext;
393     }
394 
395     /**
396      * Gets a Logger from the Context.
397      *
398      * @param name The name of the Logger to return.
399      * @return The Logger.
400      */
401     @Override
402     public Logger getLogger(final String name) {
403         return getLogger(name, null);
404     }
405 
406     /**
407      * Gets a collection of the current loggers.
408      * <p>
409      * Whether this collection is a copy of the underlying collection or not is undefined. Therefore, modify this
410      * collection at your own risk.
411      * </p>
412      *
413      * @return a collection of the current loggers.
414      */
415     public Collection<Logger> getLoggers() {
416         return loggerRegistry.getLoggers();
417     }
418 
419     /**
420      * Obtains a Logger from the Context.
421      *
422      * @param name The name of the Logger to return.
423      * @param messageFactory The message factory is used only when creating a logger, subsequent use does not change the
424      *            logger but will log a warning if mismatched.
425      * @return The Logger.
426      */
427     @Override
428     public Logger getLogger(final String name, final MessageFactory messageFactory) {
429         // Note: This is the only method where we add entries to the 'loggerRegistry' ivar.
430         Logger logger = loggerRegistry.getLogger(name, messageFactory);
431         if (logger != null) {
432             AbstractLogger.checkMessageFactory(logger, messageFactory);
433             return logger;
434         }
435 
436         logger = newInstance(this, name, messageFactory);
437         loggerRegistry.putIfAbsent(name, messageFactory, logger);
438         return loggerRegistry.getLogger(name, messageFactory);
439     }
440 
441     /**
442      * Determines if the specified Logger exists.
443      *
444      * @param name The Logger name to search for.
445      * @return True if the Logger exists, false otherwise.
446      */
447     @Override
448     public boolean hasLogger(final String name) {
449         return loggerRegistry.hasLogger(name);
450     }
451 
452     /**
453      * Determines if the specified Logger exists.
454      *
455      * @param name The Logger name to search for.
456      * @return True if the Logger exists, false otherwise.
457      */
458     @Override
459     public boolean hasLogger(final String name, final MessageFactory messageFactory) {
460         return loggerRegistry.hasLogger(name, messageFactory);
461     }
462 
463     /**
464      * Determines if the specified Logger exists.
465      *
466      * @param name The Logger name to search for.
467      * @return True if the Logger exists, false otherwise.
468      */
469     @Override
470     public boolean hasLogger(final String name, final Class<? extends MessageFactory> messageFactoryClass) {
471         return loggerRegistry.hasLogger(name, messageFactoryClass);
472     }
473 
474     /**
475      * Returns the current Configuration. The Configuration will be replaced when a reconfigure occurs.
476      *
477      * @return The Configuration.
478      */
479     public Configuration getConfiguration() {
480         return configuration;
481     }
482 
483     /**
484      * Adds a Filter to the Configuration. Filters that are added through the API will be lost when a reconfigure
485      * occurs.
486      *
487      * @param filter The Filter to add.
488      */
489     public void addFilter(final Filter filter) {
490         configuration.addFilter(filter);
491     }
492 
493     /**
494      * Removes a Filter from the current Configuration.
495      *
496      * @param filter The Filter to remove.
497      */
498     public void removeFilter(final Filter filter) {
499         configuration.removeFilter(filter);
500     }
501 
502     /**
503      * Sets the Configuration to be used.
504      *
505      * @param config The new Configuration.
506      * @return The previous Configuration.
507      */
508     private Configuration setConfiguration(final Configuration config) {
509         if (config == null) {
510             LOGGER.error("No configuration found for context '{}'.", contextName);
511             // No change, return the current configuration.
512             return this.configuration;
513         }
514         configLock.lock();
515         try {
516             final Configuration prev = this.configuration;
517             config.addListener(this);
518             executorService = Executors.newCachedThreadPool(Log4jThreadFactory.createThreadFactory(contextName));
519             executorServiceDeamons = Executors.newCachedThreadPool(Log4jThreadFactory.createDaemonThreadFactory(contextName));
520 
521             final ConcurrentMap<String, String> map = config.getComponent(Configuration.CONTEXT_PROPERTIES);
522 
523             try { // LOG4J2-719 network access may throw android.os.NetworkOnMainThreadException
524                 map.putIfAbsent("hostName", NetUtils.getLocalHostname());
525             } catch (final Exception ex) {
526                 LOGGER.debug("Ignoring {}, setting hostName to 'unknown'", ex.toString());
527                 map.putIfAbsent("hostName", "unknown");
528             }
529             map.putIfAbsent("contextName", contextName);
530             config.start();
531             this.configuration = config;
532             updateLoggers();
533             if (prev != null) {
534                 prev.removeListener(this);
535                 prev.stop();
536             }
537 
538             firePropertyChangeEvent(new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config));
539 
540             try {
541                 Server.reregisterMBeansAfterReconfigure();
542             } catch (final LinkageError | Exception e) {
543                 // LOG4J2-716: Android has no java.lang.management
544                 LOGGER.error("Could not reconfigure JMX", e);
545             }
546             // AsyncLoggers update their nanoClock when the configuration changes
547             Log4jLogEvent.setNanoClock(configuration.getNanoClock());
548 
549             return prev;
550         } finally {
551             configLock.unlock();
552         }
553     }
554 
555     private void firePropertyChangeEvent(final PropertyChangeEvent event) {
556         for (final PropertyChangeListener listener : propertyChangeListeners) {
557             listener.propertyChange(event);
558         }
559     }
560 
561     public void addPropertyChangeListener(final PropertyChangeListener listener) {
562         propertyChangeListeners.add(Objects.requireNonNull(listener, "listener"));
563     }
564 
565     public void removePropertyChangeListener(final PropertyChangeListener listener) {
566         propertyChangeListeners.remove(listener);
567     }
568 
569     /**
570      * Returns the initial configuration location or {@code null}. The returned value may not be the location of the
571      * current configuration. Use {@link #getConfiguration()}.{@link Configuration#getConfigurationSource()
572      * getConfigurationSource()}.{@link ConfigurationSource#getLocation() getLocation()} to get the actual source of the
573      * current configuration.
574      *
575      * @return the initial configuration location or {@code null}
576      */
577     public URI getConfigLocation() {
578         return configLocation;
579     }
580 
581     /**
582      * Sets the configLocation to the specified value and reconfigures this context.
583      *
584      * @param configLocation the location of the new configuration
585      */
586     public void setConfigLocation(final URI configLocation) {
587         this.configLocation = configLocation;
588 
589         reconfigure(configLocation);
590     }
591 
592     /**
593      * Reconfigures the context.
594      */
595     private void reconfigure(final URI configURI) {
596         final ClassLoader cl = ClassLoader.class.isInstance(externalContext) ? (ClassLoader) externalContext : null;
597         LOGGER.debug("Reconfiguration started for context[name={}] at URI {} ({}) with optional ClassLoader: {}",
598                 contextName, configURI, this, cl);
599         final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(this, contextName, configURI, cl);
600         if (instance == null) {
601             LOGGER.error("Reconfiguration failed: No configuration found for '{}' at '{}' in '{}'", contextName, configURI, cl);
602         } else {
603             setConfiguration(instance);
604             /*
605              * instance.start(); Configuration old = setConfiguration(instance); updateLoggers(); if (old != null) {
606              * old.stop(); }
607              */
608             final String location = configuration == null ? "?" : String.valueOf(configuration.getConfigurationSource());
609             LOGGER.debug("Reconfiguration complete for context[name={}] at URI {} ({}) with optional ClassLoader: {}",
610                     contextName, location, this, cl);
611         }
612     }
613 
614     /**
615      * Reconfigures the context. Log4j does not remove Loggers during a reconfiguration. Log4j will create new
616      * LoggerConfig objects and Log4j will point the Loggers at the new LoggerConfigs. Log4j will free the old
617      * LoggerConfig, along with old Appenders and Filters.
618      */
619     public void reconfigure() {
620         reconfigure(configLocation);
621     }
622 
623     /**
624      * Causes all Loggers to be updated against the current Configuration.
625      */
626     public void updateLoggers() {
627         updateLoggers(this.configuration);
628     }
629 
630     /**
631      * Causes all Logger to be updated against the specified Configuration.
632      *
633      * @param config The Configuration.
634      */
635     public void updateLoggers(final Configuration config) {
636         final Configuration old = this.configuration;
637         for (final Logger logger : loggerRegistry.getLoggers()) {
638             logger.updateConfiguration(config);
639         }
640         firePropertyChangeEvent(new PropertyChangeEvent(this, PROPERTY_CONFIG, old, config));
641     }
642 
643     /**
644      * Causes a reconfiguration to take place when the underlying configuration file changes.
645      *
646      * @param reconfigurable The Configuration that can be reconfigured.
647      */
648     @Override
649     public synchronized void onChange(final Reconfigurable reconfigurable) {
650         LOGGER.debug("Reconfiguration started for context {} ({})", contextName, this);
651         final Configuration newConfig = reconfigurable.reconfigure();
652         if (newConfig != null) {
653             setConfiguration(newConfig);
654             LOGGER.debug("Reconfiguration completed for {} ({})", contextName, this);
655         } else {
656             LOGGER.debug("Reconfiguration failed for {} ({})", contextName, this);
657         }
658     }
659 
660     // LOG4J2-151: changed visibility from private to protected
661     protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) {
662         return new Logger(ctx, name, messageFactory);
663     }
664 
665     /**
666      * Gets the executor service to submit normal tasks.
667      *
668      * @return the ExecutorService to submit normal tasks.
669      */
670     public ExecutorService getExecutorService() {
671         return executorService;
672     }
673 
674     /**
675      * Gets the executor service to submit daemon tasks.
676      *
677      * @return the ExecutorService to submit normal daemon tasks.
678      */
679     public ExecutorService getExecutorServiceDeamons() {
680         return executorServiceDeamons;
681     }
682 
683     /**
684      * Submits a Runnable task for normal execution and returns a Future representing that task. The Future's
685      * {@code get} method will return {@code null} upon <em>successful</em> completion.
686      *
687      * @param task the task to submit
688      * @return a Future representing pending completion of the task
689      * @throws RejectedExecutionException if the task cannot be
690      *         scheduled for execution
691      * @throws NullPointerException if the task is null
692      */
693     public Future<?> submit(final Runnable task) {
694         return executorService.submit(task);
695     }
696 
697     /**
698      * Submits a Runnable task for daemon execution and returns a Future representing that task. The Future's
699      * {@code get} method will return {@code null} upon <em>successful</em> completion.
700      *
701      * @param task
702      *            the task to submit
703      * @return a Future representing pending completion of the task
704      * @throws RejectedExecutionException
705      *             if the task cannot be scheduled for execution
706      * @throws NullPointerException
707      *             if the task is null
708      */
709     public Future<?> submitDaemon(final Runnable task) {
710         return executorServiceDeamons.submit(task);
711     }
712 
713 }