Skip to content

Commit e2072bb

Browse files
Rahul Yadavpconcannon
authored andcommitted
8245302: Upgrade LogRecord to support long thread ids and remove its usage of ThreadLocal
Java.util.logging.LogRecord has been updated to use long thread ids instead of int thread id Reviewed-by: alanb, dfuchs
1 parent f23c983 commit e2072bb

4 files changed

Lines changed: 495 additions & 42 deletions

File tree

src/java.logging/share/classes/java/util/logging/LogRecord.java

Lines changed: 102 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -26,7 +26,6 @@
2626
package java.util.logging;
2727
import java.time.Instant;
2828
import java.util.*;
29-
import java.util.concurrent.atomic.AtomicInteger;
3029
import java.util.concurrent.atomic.AtomicLong;
3130
import java.io.*;
3231
import java.security.AccessController;
@@ -75,21 +74,6 @@ public class LogRecord implements java.io.Serializable {
7574
private static final AtomicLong globalSequenceNumber
7675
= new AtomicLong(0);
7776

78-
/**
79-
* The default value of threadID will be the current thread's
80-
* thread id, for ease of correlation, unless it is greater than
81-
* MIN_SEQUENTIAL_THREAD_ID, in which case we try harder to keep
82-
* our promise to keep threadIDs unique by avoiding collisions due
83-
* to 32-bit wraparound. Unfortunately, LogRecord.getThreadID()
84-
* returns int, while Thread.getId() returns long.
85-
*/
86-
private static final int MIN_SEQUENTIAL_THREAD_ID = Integer.MAX_VALUE / 2;
87-
88-
private static final AtomicInteger nextThreadId
89-
= new AtomicInteger(MIN_SEQUENTIAL_THREAD_ID);
90-
91-
private static final ThreadLocal<Integer> threadIds = new ThreadLocal<>();
92-
9377
/**
9478
* Logging message level
9579
*/
@@ -120,6 +104,11 @@ public class LogRecord implements java.io.Serializable {
120104
*/
121105
private int threadID;
122106

107+
/**
108+
* long value of Thread ID for thread that issued logging call.
109+
*/
110+
private long longThreadID;
111+
123112
/**
124113
* The Throwable (if any) associated with log message
125114
*/
@@ -147,7 +136,10 @@ public class LogRecord implements java.io.Serializable {
147136
* @serialField sourceClassName String Class that issued logging call
148137
* @serialField sourceMethodName String Method that issued logging call
149138
* @serialField message String Non-localized raw message text
150-
* @serialField threadID int Thread ID for thread that issued logging call
139+
* @serialField threadID int this is deprecated and is available for backward compatibility.
140+
* Values may have been synthesized. If present, {@code longThreadID} represents
141+
* the actual thread id.
142+
* @serialField longThreadID long Thread ID for thread that issued logging call
151143
* @serialField millis long Truncated event time in milliseconds since 1970
152144
* - calculated as getInstant().toEpochMilli().
153145
* The event time instant can be reconstructed using
@@ -164,6 +156,7 @@ public class LogRecord implements java.io.Serializable {
164156
* @serialField resourceBundleName String Resource bundle name to localized
165157
* log message
166158
*/
159+
@Serial
167160
private static final ObjectStreamField[] serialPersistentFields =
168161
new ObjectStreamField[] {
169162
new ObjectStreamField("level", Level.class),
@@ -172,6 +165,7 @@ public class LogRecord implements java.io.Serializable {
172165
new ObjectStreamField("sourceMethodName", String.class),
173166
new ObjectStreamField("message", String.class),
174167
new ObjectStreamField("threadID", int.class),
168+
new ObjectStreamField("longThreadID", long.class),
175169
new ObjectStreamField("millis", long.class),
176170
new ObjectStreamField("nanoAdjustment", int.class),
177171
new ObjectStreamField("thrown", Throwable.class),
@@ -184,20 +178,22 @@ public class LogRecord implements java.io.Serializable {
184178
private transient ResourceBundle resourceBundle;
185179

186180
/**
187-
* Returns the default value for a new LogRecord's threadID.
181+
* Synthesizes a pseudo unique integer value from a long {@code id} value.
182+
* For backward compatibility with previous releases,the returned integer is
183+
* such that for any positive long less than or equals to {@code Integer.MAX_VALUE},
184+
* the returned integer is equal to the original value.
185+
* Otherwise - it is synthesized with a best effort hashing algorithm,
186+
* and the returned value is negative.
187+
* Calling this method multiple times with the same value always yields the same result.
188+
*
189+
* @return thread id
188190
*/
189-
private int defaultThreadID() {
190-
long tid = Thread.currentThread().getId();
191-
if (tid < MIN_SEQUENTIAL_THREAD_ID) {
192-
return (int) tid;
193-
} else {
194-
Integer id = threadIds.get();
195-
if (id == null) {
196-
id = nextThreadId.getAndIncrement();
197-
threadIds.set(id);
198-
}
199-
return id;
200-
}
191+
192+
private int shortThreadID(long id) {
193+
if (id >= 0 && id <= Integer.MAX_VALUE)
194+
return (int) id;
195+
int hash = Long.hashCode(id);
196+
return hash < 0 ? hash : (-1 - hash);
201197
}
202198

203199
/**
@@ -225,10 +221,13 @@ public LogRecord(Level level, String msg) {
225221
message = msg;
226222
// Assign a thread ID and a unique sequence number.
227223
sequenceNumber = globalSequenceNumber.getAndIncrement();
228-
threadID = defaultThreadID();
224+
long id = Thread.currentThread().getId();
225+
// threadID is deprecated and this value is synthesised for backward compatibility
226+
threadID = shortThreadID(id);
227+
longThreadID = id;
229228
instant = Instant.now();
230229
needToInferCaller = true;
231-
}
230+
}
232231

233232
/**
234233
* Get the source Logger's name.
@@ -447,18 +446,54 @@ public void setParameters(Object parameters[]) {
447446
* This is a thread identifier within the Java VM and may or
448447
* may not map to any operating system ID.
449448
*
449+
* @deprecated Values returned by this method may be synthesized,
450+
* and may not correspond to the actual {@linkplain Thread#getId() thread id},
451+
* use {@link #getLongThreadID()} instead.
450452
* @return thread ID
451453
*/
454+
@Deprecated(since = "16")
452455
public int getThreadID() {
453456
return threadID;
454457
}
455458

456459
/**
457460
* Set an identifier for the thread where the message originated.
458461
* @param threadID the thread ID
462+
*
463+
* @deprecated This method doesn't allow to pass a long {@linkplain Thread#getId() thread id},
464+
* use {@link #setLongThreadID(long)} instead.
459465
*/
466+
@Deprecated(since = "16")
460467
public void setThreadID(int threadID) {
461468
this.threadID = threadID;
469+
this.longThreadID = threadID;
470+
}
471+
472+
/**
473+
* Get a thread identifier for the thread where message originated
474+
*
475+
* <p>
476+
* This is a thread identifier within the Java VM and may or
477+
* may not map to any operating system ID.
478+
*
479+
* @return thread ID
480+
* @since 16
481+
*/
482+
public long getLongThreadID() {
483+
return longThreadID;
484+
}
485+
486+
/**
487+
* Set an identifier for the thread where the message originated.
488+
*
489+
* @param longThreadID the thread ID
490+
* @return this LogRecord
491+
* @since 16
492+
*/
493+
public LogRecord setLongThreadID(long longThreadID) {
494+
this.threadID = shortThreadID(longThreadID);
495+
this.longThreadID = longThreadID;
496+
return this;
462497
}
463498

464499
/**
@@ -552,6 +587,7 @@ public void setThrown(Throwable thrown) {
552587
this.thrown = thrown;
553588
}
554589

590+
@Serial
555591
private static final long serialVersionUID = 5372048053134512534L;
556592

557593
/**
@@ -564,6 +600,7 @@ public void setThrown(Throwable thrown) {
564600
* a null String is written. Otherwise the output of Object.toString()
565601
* is written.
566602
*/
603+
@Serial
567604
private void writeObject(ObjectOutputStream out) throws IOException {
568605
// We have to write serialized fields first.
569606
ObjectOutputStream.PutField pf = out.putFields();
@@ -573,6 +610,7 @@ private void writeObject(ObjectOutputStream out) throws IOException {
573610
pf.put("sourceMethodName", sourceMethodName);
574611
pf.put("message", message);
575612
pf.put("threadID", threadID);
613+
pf.put("longThreadID", longThreadID);
576614
pf.put("millis", instant.toEpochMilli());
577615
pf.put("nanoAdjustment", instant.getNano() % 1000_000);
578616
pf.put("thrown", thrown);
@@ -594,16 +632,40 @@ private void writeObject(ObjectOutputStream out) throws IOException {
594632
}
595633
}
596634

635+
/**
636+
* Initializes the LogRecord from deserialized data.
637+
* <ul>
638+
* <li>If {@code longThreadID} is present in the serial form, its value
639+
* takes precedence over {@code threadID} and a value for {@code threadID}
640+
* is synthesized from it, such that for {@code longThreadID} values between
641+
* {@code 0} and {@code Integer.MAX_VALUE} inclusive, {@code longThreadID}
642+
* and {@code threadID} will have the same value. For values outside of this
643+
* range a negative synthesized value will be deterministically derived
644+
* from {@code longThreadID}.
645+
* <li>Otherwise, when only {@code threadID} is
646+
* present, {@code longThreadID} is initialized with the value of
647+
* {@code threadID} which may be anything between {@code Integer.MIN_VALUE}
648+
* and {Integer.MAX_VALUE}.
649+
* </ul>
650+
*/
651+
@Serial
597652
private void readObject(ObjectInputStream in)
598-
throws IOException, ClassNotFoundException {
653+
throws IOException, ClassNotFoundException {
599654
// We have to read serialized fields first.
600655
ObjectInputStream.GetField gf = in.readFields();
601656
level = (Level) gf.get("level", null);
602657
sequenceNumber = gf.get("sequenceNumber", 0L);
603658
sourceClassName = (String) gf.get("sourceClassName", null);
604659
sourceMethodName = (String) gf.get("sourceMethodName", null);
605660
message = (String) gf.get("message", null);
606-
threadID = gf.get("threadID", 0);
661+
// If longthreadID is not present, it will be initialised with threadID value
662+
// If longthreadID is present, threadID might have a synthesized value
663+
int threadID = gf.get("threadID", 0);
664+
long longThreadID = gf.get("longThreadID", (long)threadID);
665+
if (threadID != longThreadID)
666+
threadID = shortThreadID(longThreadID);
667+
this.threadID = threadID;
668+
this.longThreadID = longThreadID;
607669
long millis = gf.get("millis", 0L);
608670
int nanoOfMilli = gf.get("nanoAdjustment", 0);
609671
instant = Instant.ofEpochSecond(
@@ -641,9 +703,9 @@ private void readObject(ObjectInputStream in)
641703
// use system class loader to ensure the ResourceBundle
642704
// instance is a different instance than null loader uses
643705
final ResourceBundle bundle =
644-
ResourceBundle.getBundle(resourceBundleName,
645-
Locale.getDefault(),
646-
ClassLoader.getSystemClassLoader());
706+
ResourceBundle.getBundle(resourceBundleName,
707+
Locale.getDefault(),
708+
ClassLoader.getSystemClassLoader());
647709
resourceBundle = bundle;
648710
} catch (MissingResourceException ex) {
649711
// This is not a good place to throw an exception,
@@ -697,7 +759,7 @@ static final class CallerFinder implements Predicate<StackWalker.StackFrame> {
697759
private static final StackWalker WALKER;
698760
static {
699761
final PrivilegedAction<StackWalker> action =
700-
() -> StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
762+
() -> StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
701763
WALKER = AccessController.doPrivileged(action);
702764
}
703765

@@ -736,7 +798,7 @@ public boolean test(StackWalker.StackFrame t) {
736798

737799
private boolean isLoggerImplFrame(String cname) {
738800
return (cname.equals("java.util.logging.Logger") ||
739-
cname.startsWith("sun.util.logging.PlatformLogger"));
801+
cname.startsWith("sun.util.logging.PlatformLogger"));
740802
}
741803
}
742804
}

src/java.logging/share/classes/java/util/logging/XMLFormatter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -219,7 +219,7 @@ public String format(LogRecord record) {
219219
}
220220

221221
sb.append(" <thread>");
222-
sb.append(record.getThreadID());
222+
sb.append(record.getLongThreadID());
223223
sb.append("</thread>\n");
224224

225225
if (record.getMessage() != null) {
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
25+
26+
/*
27+
* @test
28+
* @bug 8245302
29+
* @summary test the relationship between
30+
* thread id long and int methods
31+
* @build LogRecordThreadIdTest
32+
* @run testng/othervm LogRecordThreadIdTest
33+
*/
34+
35+
import java.util.logging.Level;
36+
import java.util.logging.LogRecord;
37+
import org.testng.annotations.BeforeTest;
38+
import org.testng.annotations.Test;
39+
import static org.testng.Assert.assertEquals;
40+
import static org.testng.Assert.assertNotEquals;
41+
42+
43+
44+
public class LogRecordThreadIdTest {
45+
46+
LogRecord record, record1, record2;
47+
48+
@BeforeTest
49+
public void setUp() throws Exception {
50+
record = new LogRecord(Level.INFO, "record");
51+
record1 = new LogRecord(Level.INFO, "record1");
52+
record2 = new LogRecord(Level.INFO, "record2");
53+
}
54+
55+
/**
56+
* Tests threadID setter methods for consistency
57+
* with longThreadID
58+
*/
59+
@Test
60+
public void testSetThreadId() {
61+
record.setThreadID(Integer.MAX_VALUE - 20);
62+
record1.setThreadID(Integer.MAX_VALUE - 1);
63+
assertEquals(record.getLongThreadID(), Integer.MAX_VALUE - 20L);
64+
assertEquals(record.getThreadID(), Integer.MAX_VALUE - 20);
65+
assertEquals(record1.getThreadID(), Integer.MAX_VALUE - 1);
66+
assertEquals(record1.getLongThreadID(), Integer.MAX_VALUE - 1);
67+
}
68+
69+
/**
70+
* Tests longThreadID methods for consistency
71+
* with threadID
72+
*/
73+
@Test
74+
public void testSetLongThreadId() {
75+
record.setLongThreadID(Integer.MAX_VALUE - 20L);
76+
record1.setLongThreadID(Integer.MAX_VALUE + 10L);
77+
record2.setLongThreadID(Integer.MAX_VALUE);
78+
assertEquals(record.getThreadID(), Integer.MAX_VALUE - 20);
79+
assertEquals(record.getLongThreadID(), Integer.MAX_VALUE - 20L);
80+
assertNotEquals(record1.getThreadID(), Integer.MAX_VALUE + 10L);
81+
assertEquals(record1.getLongThreadID(), Integer.MAX_VALUE + 10L);
82+
assertEquals(record2.getThreadID(), Integer.MAX_VALUE);
83+
assertEquals(record2.getLongThreadID(), Integer.MAX_VALUE);
84+
85+
}
86+
}

0 commit comments

Comments
 (0)