Skip to content

Commit 1232d7b

Browse files
committed
Ensure singleton object creation thread safety
This fixes possible race conditions creating the singleton instances of: - `BeaconManager` - `DetectionTracker` - `MonitoringStatus` - `Stats` Both `DetectionTracker` and `Stats` require no argument to their constructors. The singleton instances are also both `private` to the class. This means we can create the singleton instance as a constant without worry about any additional threading issues; Java guarantees constant creation thread safety (see _Effective Java_, Second Edition, _Item 3: Enforce the singleton property with a private constructor or an enum type_). However, both `BeaconManager` and `MonitoringStatus` require the application context in order to be created. This means we cannot use a constant since we won't have the application context instance available at constant creation time. Instead this uses the lazy-loading double check locking pattern with a private lock object. While this double check locking idiom may seem a bit odd it is a well published and tested pattern. The source of the idiom is cited in the local code comments. We also change the instances to be `volatile` to ensure that any thread that reads the field will see the most recently written value - which is necessary since there is no locking when we have already initialized the field. For more details on this pattern see _Effective Java_, Second Edition, _Item 71: Use lazy initialization judiciously_. This additionally fixes a possible denial-of-service condition with the previous `MonitoringStatus` singleton implementation. This DOS issue can occur because the synchronization is performed by locking the externally accessible `MonitoringStatus.class` object. An external thread can also attempt to lock on the class object which could lead to a dead-lock situation. While it's unlikely it's still possible and this new implementation guarantees that won't happen due to the use of the private lock object (see _Effective Java_, Second Edition, _Item 70: Document thread safety_). References: Joshua Bloch. _Effective Java_, Second Edition. Addison-Wesley, 2008
1 parent 016bb44 commit 1232d7b

File tree

5 files changed

+63
-22
lines changed

5 files changed

+63
-22
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ Bug Fixes:
88
- Fix bug causing brief scan dropouts after starting a scan after a long period
99
of inactivity (i.e. startup and background-foreground transitions) due to
1010
Android N scan limits (#489, Aaron Kromer)
11+
- Ensure thread safety for singleton creation of `BeaconManager`,
12+
`DetectionTracker`, `MonitoringStatus`, and `Stats`. (#494, Aaron Kromer)
1113

1214

1315
### 2.9.2 / 2016-11-22

src/main/java/org/altbeacon/beacon/BeaconManager.java

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@
108108
public class BeaconManager {
109109
private static final String TAG = "BeaconManager";
110110
private Context mContext;
111-
protected static BeaconManager client = null;
111+
protected static volatile BeaconManager client = null;
112112
private final ConcurrentMap<BeaconConsumer, ConsumerInfo> consumers = new ConcurrentHashMap<BeaconConsumer,ConsumerInfo>();
113113
private Messenger serviceMessenger = null;
114114
protected final Set<RangeNotifier> rangeNotifiers = new CopyOnWriteArraySet<>();
@@ -123,6 +123,11 @@ public class BeaconManager {
123123
private static boolean sAndroidLScanningDisabled = false;
124124
private static boolean sManifestCheckingDisabled = false;
125125

126+
/**
127+
* Private lock object for singleton initialization protecting against denial-of-service attack.
128+
*/
129+
private static final Object SINGLETON_LOCK = new Object();
130+
126131
/**
127132
* Set to true if you want to show library debugging.
128133
*
@@ -239,11 +244,29 @@ public static long getRegionExitPeriod(){
239244
* or non-Service class, you can attach it to another singleton or a subclass of the Android Application class.
240245
*/
241246
public static BeaconManager getInstanceForApplication(Context context) {
242-
if (client == null) {
243-
LogManager.d(TAG, "BeaconManager instance creation");
244-
client = new BeaconManager(context);
247+
/*
248+
* Follow double check pattern from Effective Java v2 Item 71.
249+
*
250+
* Bloch recommends using the local variable for this for performance reasons:
251+
*
252+
* > What this variable does is ensure that `field` is read only once in the common case
253+
* > where it's already initialized. While not strictly necessary, this may improve
254+
* > performance and is more elegant by the standards applied to low-level concurrent
255+
* > programming. On my machine, [this] is about 25 percent faster than the obvious
256+
* > version without a local variable.
257+
*
258+
* Joshua Bloch. Effective Java, Second Edition. Addison-Wesley, 2008. pages 283-284
259+
*/
260+
BeaconManager instance = client;
261+
if (instance == null) {
262+
synchronized (SINGLETON_LOCK) {
263+
instance = client;
264+
if (instance == null) {
265+
client = instance = new BeaconManager(context);
266+
}
267+
}
245268
}
246-
return client;
269+
return instance;
247270
}
248271

249272
protected BeaconManager(Context context) {

src/main/java/org/altbeacon/beacon/service/DetectionTracker.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,14 @@
66
* Created by dyoung on 1/10/15.
77
*/
88
public class DetectionTracker {
9-
private static DetectionTracker sDetectionTracker = null;
9+
private static final DetectionTracker INSTANCE = new DetectionTracker();
10+
1011
private long mLastDetectionTime = 0l;
1112
private DetectionTracker() {
1213

1314
}
14-
public static synchronized DetectionTracker getInstance() {
15-
if (sDetectionTracker == null) {
16-
sDetectionTracker = new DetectionTracker();
17-
}
18-
return sDetectionTracker;
15+
public static DetectionTracker getInstance() {
16+
return INSTANCE;
1917
}
2018
public long getLastDetectionTime() {
2119
return mLastDetectionTime;

src/main/java/org/altbeacon/beacon/service/MonitoringStatus.java

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import static android.content.Context.MODE_PRIVATE;
2424

2525
public class MonitoringStatus {
26-
private static MonitoringStatus sInstance;
26+
private static volatile MonitoringStatus sInstance;
2727
private static final int MAX_REGIONS_FOR_STATUS_PRESERVATION = 50;
2828
private static final int MAX_STATUS_PRESERVATION_FILE_AGE_TO_RESTORE_SECS = 60 * 15;
2929
private static final String TAG = MonitoringStatus.class.getSimpleName();
@@ -35,15 +35,35 @@ public class MonitoringStatus {
3535

3636
private boolean mStatePreservationIsOn = true;
3737

38+
/**
39+
* Private lock object for singleton initialization protecting against denial-of-service attack.
40+
*/
41+
private static final Object SINGLETON_LOCK = new Object();
42+
3843
public static MonitoringStatus getInstanceForApplication(Context context) {
39-
if (sInstance == null) {
40-
synchronized (MonitoringStatus.class) {
41-
if (sInstance == null) {
42-
sInstance = new MonitoringStatus(context.getApplicationContext());
44+
/*
45+
* Follow double check pattern from Effective Java v2 Item 71.
46+
*
47+
* Bloch recommends using the local variable for this for performance reasons:
48+
*
49+
* > What this variable does is ensure that `field` is read only once in the common case
50+
* > where it's already initialized. While not strictly necessary, this may improve
51+
* > performance and is more elegant by the standards applied to low-level concurrent
52+
* > programming. On my machine, [this] is about 25 percent faster than the obvious
53+
* > version without a local variable.
54+
*
55+
* Joshua Bloch. Effective Java, Second Edition. Addison-Wesley, 2008. pages 283-284
56+
*/
57+
MonitoringStatus instance = sInstance;
58+
if (instance == null) {
59+
synchronized (SINGLETON_LOCK) {
60+
instance = sInstance;
61+
if (instance == null) {
62+
sInstance = instance = new MonitoringStatus(context.getApplicationContext());
4363
}
4464
}
4565
}
46-
return sInstance;
66+
return instance;
4767
}
4868

4969
public MonitoringStatus(Context context) {

src/main/java/org/altbeacon/beacon/service/Stats.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
* Created by dyoung on 10/16/14.
1313
*/
1414
public class Stats {
15+
private static final Stats INSTANCE = new Stats();
1516
private static final String TAG = "Stats";
1617
private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("HH:mm:ss.SSS");
1718

@@ -21,14 +22,11 @@ public class Stats {
2122
private boolean mEnableHistoricalLogging;
2223
private boolean mEnabled;
2324
private Sample mSample;
24-
private static Stats mInstance;
2525

2626
public static Stats getInstance() {
27-
if(mInstance == null) {
28-
mInstance = new Stats();
29-
}
30-
return mInstance;
27+
return INSTANCE;
3128
}
29+
3230
private Stats() {
3331
mSampleIntervalMillis = 0l;
3432
clearSamples();

0 commit comments

Comments
 (0)