RFR: 8023168 - Cleanup LogManager class initialization and LogManager/LoggerContext relationship

Peter Levart peter.levart at gmail.com
Thu Sep 5 17:15:36 UTC 2013


On 09/05/2013 02:43 PM, Daniel Fuchs wrote:
> On 9/5/13 1:51 PM, Peter Levart wrote:
>>> Threads that call ensureLogManagerInitialized() after that will find
>>> the flag
>>> is true before entering the synchronized block and will return 
>>> directly.
>>
>> ...and such threads can see rootLogger field being either still null or
>> not null but the Logger instance that is obtained via such data race can
>> be half-initialized.
>
> I don't think so, because the flag isn't set to true until
> the initialization is finished.
>
> BTW - there was a reason for having both an initializationCalled
> *and* a synchronized at method level. The purpose of
> initializationCalled flag was to prevent infinite recursion in the
> same thread - because addLogger within ensureLogManagerInitialized
> will trigger a new call to ensureLogManagerInitialized that the
> synchronized won't block - since it's in the same thread.

Oh, I see. The ensureLogManagerInitialized() is also called from 
requiresDefaultLoggers() which is called from addLocalLogger(Logger) 
which is called from addLogger(Logger) which is also called from 
ensureLogManagerInitialized().

I suspect that calling ensureLogManagerInitialized() from 
requiresDefaultLoggers() is not needed (any more). Why?

- the ensureLogManagerInitialized() is only meant for the global 
LogManager (LogManager.manager). It has no effect on private LogManagers.
- almost all accesses to the global LogManager are done via 
LogManager.getLogManager() which already calls 
ensureLogManagerInitialized() before returning the LogManager.manager 
instance.
- the only other places where private LogManager.manager field is 
accessed directly are:
     - in the ensureLogManagerInitialized() method itself to skip 
initialization for private LogManagers
     - in the LoggerContext.requiresDefaultLoggers() to check if the 
owner is the global LogManager (an reference equality check)
     - in the Cleaner.run() to prevent premature GC of global LogManager

All those direct LogManager.manager field usages do not call any methods 
on the so obtained global LogManager, so I conclude that any code that 
calls any instance method on global LogManager must have obtained it 
from the LogManager.getLogManager() method and this method already 
ensures that it is initialized.

If you remove a call to ensureLogManagerInitialized() from 
LoggerContext.requiresDefaultLoggers() then there is one less 
possibility for recursion. The other is in Logger.getGlobal(). Is this 
needed? I think not:

     public static final Logger getGlobal() {

         if (global != null && global.manager == null) {
             global.manager = LogManager.getLogManager();
         }

         if (global.manager !=  null) {
             global.manager.ensureLogManagerInitialized();
         }

         return global;
     }


Static initialization of Logger class now has no dependency on 
LogManager, so there should be no recursion in static initialization. 
The 1st call to Logger.getGlobal() should find global != null and 
global.manager == null and so LogManager.getLogManager() should be 
called but that call does not recurse because LogManager obtains the 
global Logger directly from the deprecated Logger.global field, so the 
global.manager should be assigned with the initialized global 
LogManager. No need to call global.manager.ensureLogManagerInitialized() 
later.

The method could simply be:

     public static final Logger getGlobal() {

         if (global.manager == null) {
             global.manager = LogManager.getLogManager();
         }

         return global;
     }


Now that both recursive calls to ensureLogManagerInitialized() are 
removed, there's no need to check for recursive invocation in the 
ensureLogManagerInitialized() and initializationCalled flag can be removed.


>
> So I need in fact two flags: one initializationDone - which will
> be volatile - the other initializationCalled - which doesn't need
> to be since it will be always written/read from within the
> synchronized block.
>
> I toyed with the idea of using a single volatile three valued byte
> instead:
>
> initialization: 0 => uninitialized, 1 => initializing, 2 => initialized
>
> I opted for the two booleans - but the three valued byte might be more
> efficient:
>
> 1)
>
> volatile byte initialization = 0;
>
> ensureLogManagerInitialized() {
>   if (initialization == 2) return; // already done
>   synchronized(this) {
>     if (initialization > 0) return;
>     initialization = 1; // avoids recursion
>     try {
>       // do stuff which might recurse on
>       // ensureLogManagerInitialized()
>     } finally {
>       initialization = 2; // mark as done
>     }
>   }
> }
>
> vs 2)
>
> volatile boolean initializationDone = false;
> boolean initializationCalled = false;
>
> ensureLogManagerInitialized() {
>   if (initializationDone) return; // already done
>   synchronized(this) {
>     if (initializedCalled || initializationDone) return;
>     initializedCalled = true; // avoids recursion
>     try {
>       // do stuff which might recurse on
>       // ensureLogManagerInitialized()
>     } finally {
>         initializationDone = true; // mark as done.
>     }
>   }
> }
>
> Don't know if you have a preference for one or the other.
> My current impl (still under test) is 2)

After re-checking the JMM cookbook 
(http://gee.cs.oswego.edu/dl/jmm/cookbook.html), I see that you were 
right in the following:

volatile boolean initializationDone;
Logger rootLogger;

Thread1:

rootLogger = new RootLogger();
initializationDone = true;

Thread2:

if (initializationDone) {
    ...
    Logger logger = rootLogger;
    ...use logger...
}

A normal write followed by volatile write can not be reordered.
A volatile read followed by normal read can not be reordered either.

So if every read access of rootLogger field is performed via some call 
to LogManager instance method and an instance of global LogManager can 
only be obtained via LogManager.getLogManager() which calls 
ensureLogManagerInitialized(), the synchronization should be OK.


Regards, Peter

> best regards,
>
> -- daniel
>
>>
>>> I can make rootLogger volatile. I'm not 100% sure it is required - but
>>> I think
>>> it is more correct, and as such it will make the code more 
>>> maintainable.
>>> Good point. It's probably  something I would have overlooked.
>>
>>
>> I think volatile rootLogger field is required for correct publication of
>> RootLogger instances if double-checked locking is used...
>>
>>
>> Regards, Peter
>>
>




More information about the core-libs-dev mailing list