Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8247219

Upgrade LogRecord to support long thread ids and remove its usage of ThreadLocal

    XMLWordPrintable

    Details

    • Type: CSR
    • Status: Closed
    • Priority: P3
    • Resolution: Approved
    • Fix Version/s: 16
    • Component/s: core-libs
    • Labels:
      None
    • Subcomponent:
    • Compatibility Kind:
      behavioral
    • Compatibility Risk:
      low
    • Compatibility Risk Description:
      Hide
      The algorithm used to compute the int thread id from the long thread id is changed from using an incremental counter and a ThreadLocal to using a deterministic hash algorithm. The range for which the int thread id has the same value than the long thread id is extended to Integer.MAX_VALUE (where it was previously half of that). The serial compatibility of the LogRecord is preserved. The XMLFormatter will now potentially issue 64bits value for <thread> where before it would only issue 32bits value. However, this was (and still is) defined as #PCDATA in the DTD: https://docs.oracle.com/javase/8/docs/technotes/guides/logging/overview.html#a3.0.
      Show
      The algorithm used to compute the int thread id from the long thread id is changed from using an incremental counter and a ThreadLocal to using a deterministic hash algorithm. The range for which the int thread id has the same value than the long thread id is extended to Integer.MAX_VALUE (where it was previously half of that). The serial compatibility of the LogRecord is preserved. The XMLFormatter will now potentially issue 64bits value for <thread> where before it would only issue 32bits value. However, this was (and still is) defined as #PCDATA in the DTD: https://docs.oracle.com/javase/8/docs/technotes/guides/logging/overview.html#a3.0 .
    • Interface Kind:
      Java API
    • Scope:
      SE

      Description

      Summary

      java.util.logging.LogRecord is updated to support long thread ids. New accessors to support long thread ids are added, and previous accessors working with int are deprecated.

      Problem

      LogRecord handles thread id as an int. This is a long standing behavior that precludes the addition of the Thread::getId method which returns a long. Prior to Java 5, there was no correlation between the LogRecord::getThreadID and the thread id returned by Thread::getId. In Java 5, JDK-6278014 fixed that to ensure that if Thread::getId returned a value less than Integer.MAX_VALUE / 2, it would be directly used as the LogRecord threadID, otherwise, a value would be synthesized, by incrementing a counter. In order to ensure that the same value would always be used for the same thread a ThreadLocal<Integer> was used. The rationale was that it was very unlikely that a system would use more thread than Integer.MAX_VALUE / 2 - so the it would be unlikely to get a syntesized value. However, this assumption needs to be revisited in the context of virtual threads, where billion of virtual threads might not be so unlikely, and where using ThreadLocal could become a performance drain.

      This CSR proposes a solution to upgrade LogRecord to support long thread ids and get rid of the ThreadLocal.

      Solution

      LogRecord is updated with the following changes:

      • The serial field threadID is deprecated, but is kept for backward compatibility.
      • getThreadID and setThreadID methods are deprecated in favor of new getLongThreadID and setLongThreadID accessors.
      • A new serial field longThreadID is introduced to carry the long thread id as returned by Thread::getId.
      • long thread ids that are less than Integer.MAX_VALUE are directly mapped to threadID (and longThreadID). This extends the range where the deprecated threadID has the same value than the actual long thread id returned by Thread::getId.
      • For long thread id greater than Integer.MAX_VALUE, a new negative value for the int threadID is synthesized, using a deterministic algorithm based on the longThreadID hash, and such that the resulting value is guaranteed to be a negative value.
      • However, the new longThreadID field always carries the original long thread id - and new accessors are provided to get/set this field.

      In addition - and for maintaining backward compatibility, the following invariant are preserved:

      • setting the thread id as an int (using the deprecated setters) sets both fields to the same int value.
      • setting the thread id as a long (using the new setter) sets the new longThreadID field to the provided value, and the old threadID to a synthesized value as described above.

      The exact algorithm used to synthesize a value for the int value is not documented (it wasn't documented before either) but the documentation, and deprecation of the old accessors, will incite users of the API to use the new longThreadID.

      Specification

       src/java.logging/share/classes/java/util/logging/LogRecord.java
      
       /**
        * @serialField level Level Logging message level
        * @serialField sequenceNumber long Sequence number
        * @serialField sourceClassName String Class that issued logging call
        * @serialField sourceMethodName String Method that issued logging call
        * @serialField message String Non-localized raw message text
      - * @serialField threadID int Thread ID for thread that issued logging call     
      + * @serialField threadID int this is deprecated and is available for backward compatibility.
      + *              Values may have been synthesized. If present, {@code longThreadID} represents
      + *              the actual thread id    
      + * @serialField longThreadID long Thread ID for thread that issued logging call
        * @serialField millis long Truncated event time in milliseconds since 1970
        *              - calculated as getInstant().toEpochMilli().
        *               The event time instant can be reconstructed using
        * <code>Instant.ofEpochSecond(millis/1000, (millis % 1000) * 1000_000 + nanoAdjustment)</code>
        * @serialField nanoAdjustment int Nanoseconds adjustment to the millisecond of
        *              event time - calculated as getInstant().getNano() % 1000_000
        *               The event time instant can be reconstructed using
        * <code>Instant.ofEpochSecond(millis/1000, (millis % 1000) * 1000_000 + nanoAdjustment)</code>
        *              <p>
        *              Since: 9
        * @serialField thrown Throwable The Throwable (if any) associated with log
        *              message
        * @serialField loggerName String Name of the source Logger
        * @serialField resourceBundleName String Resource bundle name to localized
        *              log message
        */

      ...

      /**
      + * Initializes the LogRecord from deserialized data.
      + * <ul>
      + * <li>If {@code longThreadID} is present in the serial form, its value
      + * takes precedence over {@code threadID} and a value for {@code threadID}
      + * is synthesized from it, such that for {@code longThreadID} values between
      + * {@code 0} and {@code Integer.MAX_VALUE} inclusive, {@code longThreadID}
      + * and {@code threadID} will have the same value. For values outside of this
      + * range a negative synthesized value will be deterministically derived
      + * from {@code longThreadID}.
      + * <li>Otherwise, when only {@code threadID} is
      + * present, {@code longThreadID} is initialized with the value of
      + * {@code threadID} which may be anything between {@code Integer.MIN_VALUE}
      + * and {Integer.MAX_VALUE}.
      + * </ul>
      +  */
      + @Serial
      private void readObject(ObjectInputStream in)

      ...

       /**
        * Get an identifier for the thread where the message originated.
        * <p>
        * This is a thread identifier within the Java VM and may or
        * may not map to any operating system ID.
        *
      + * @deprecated  Values returned by this method may be synthesized,
      + *              and may not correspond to the actual {@linkplain Thread#getId() thread id}.
      + *              Use {@link #getLongThreadID()} instead
        * @return thread ID
        */
      + @Deprecated(since = "16")
        public int getThreadID() {

      ...

      /**
        * Set an identifier for the thread where the message originated.
        * @param threadID  the thread ID
      + *
      + * @deprecated  This method doesn't allow to pass a long {@linkplain Thread#getId() thread id}.
      + *              Use {@link #setLongThreadID(long)} setLongThreadID(long longThreadID)} instead
        */
      + @Deprecated(since = "16")
        public void setThreadID(int threadID) {

      ...

      **
      + * Get a thread identifier for the thread where message originated
      + *
      + * <p>
      + * This is a thread identifier within the Java VM and may or
      + * may not map to any operating system ID.
      + *
      + * @return thread ID
      + * @since 16
      + */
      +  public long getLongThreadID()

      ...

       /**
      + * Set an identifier for the thread where the message originated.
      + *
      + * @param longThreadID the thread ID
      + * @return this log record
      + * @since 16
      + */
      +  public LogRecord setLongThreadID(long longThreadID)

        Attachments

          Issue Links

            Activity

              People

              Assignee:
              ryadav Rahul Yadav
              Reporter:
              alanb Alan Bateman
              Reviewed By:
              Alan Bateman, Daniel Fuchs
              Votes:
              0 Vote for this issue
              Watchers:
              4 Start watching this issue

                Dates

                Created:
                Updated:
                Resolved: