/* * Copyright (c) 2015-present, Parse, LLC. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.parse; import bolts.Continuation; import bolts.Task; /** package */ class CachedCurrentInstallationController implements ParseCurrentInstallationController { /* package */ static final String TAG = "com.parse.CachedCurrentInstallationController"; /* * Note about lock ordering: * * You must NOT acquire the ParseInstallation instance mutex (the "mutex" field in ParseObject) * while holding this current installation lock. (We used to use the ParseInstallation.class lock, * but moved on to an explicit lock object since anyone could acquire the ParseInstallation.class * lock as ParseInstallation is a public class.) Acquiring the instance mutex while holding this * current installation lock will lead to a deadlock. Here is an example: * https://phabricator.fb.com/P3251091 */ private final Object mutex = new Object(); private final TaskQueue taskQueue = new TaskQueue(); private final ParseObjectStore store; private final InstallationId installationId; // The "current installation" is the installation for this device. Protected by // mutex. /* package for test */ ParseInstallation currentInstallation; public CachedCurrentInstallationController( ParseObjectStore store, InstallationId installationId) { this.store = store; this.installationId = installationId; } @Override public Task setAsync(final ParseInstallation installation) { if (!isCurrent(installation)) { return Task.forResult(null); } return taskQueue.enqueue(new Continuation>() { @Override public Task then(Task toAwait) throws Exception { return toAwait.continueWithTask(new Continuation>() { @Override public Task then(Task task) throws Exception { return store.setAsync(installation); } }).continueWithTask(new Continuation>() { @Override public Task then(Task task) throws Exception { installationId.set(installation.getInstallationId()); return task; } }, ParseExecutors.io()); } }); } @Override public Task getAsync() { synchronized (mutex) { if (currentInstallation != null) { return Task.forResult(currentInstallation); } } return taskQueue.enqueue(new Continuation>() { @Override public Task then(Task toAwait) throws Exception { return toAwait.continueWithTask(new Continuation>() { @Override public Task then(Task task) throws Exception { synchronized (mutex) { if (currentInstallation != null) { return Task.forResult(currentInstallation); } } return store.getAsync().continueWith(new Continuation() { @Override public ParseInstallation then(Task task) throws Exception { ParseInstallation current = task.getResult(); if (current == null) { current = ParseObject.create(ParseInstallation.class); current.updateDeviceInfo(installationId); } else { installationId.set(current.getInstallationId()); PLog.v(TAG, "Successfully deserialized Installation object"); } synchronized (mutex) { currentInstallation = current; } return current; } }, ParseExecutors.io()); } }); } }); } @Override public Task existsAsync() { synchronized (mutex) { if (currentInstallation != null) { return Task.forResult(true); } } return taskQueue.enqueue(new Continuation>() { @Override public Task then(Task toAwait) throws Exception { return toAwait.continueWithTask(new Continuation>() { @Override public Task then(Task task) throws Exception { return store.existsAsync(); } }); } }); } @Override public void clearFromMemory() { synchronized (mutex) { currentInstallation = null; } } @Override public void clearFromDisk() { synchronized (mutex) { currentInstallation = null; } try { installationId.clear(); ParseTaskUtils.wait(store.deleteAsync()); } catch (ParseException e) { // ignored } } @Override public boolean isCurrent(ParseInstallation installation) { synchronized (mutex) { return currentInstallation == installation; } } }