Loading services/core/java/com/android/server/job/JobPackageTracker.java +17 −7 Original line number Diff line number Diff line Loading @@ -39,19 +39,23 @@ public final class JobPackageTracker { public static final int EVENT_NULL = 0; public static final int EVENT_START_JOB = 1; public static final int EVENT_STOP_JOB = 2; public static final int EVENT_START_PERIODIC_JOB = 3; public static final int EVENT_STOP_PERIODIC_JOB = 4; private final RingBufferIndices mEventIndices = new RingBufferIndices(EVENT_BUFFER_SIZE); private final int[] mEventCmds = new int[EVENT_BUFFER_SIZE]; private final long[] mEventTimes = new long[EVENT_BUFFER_SIZE]; private final int[] mEventUids = new int[EVENT_BUFFER_SIZE]; private final String[] mEventTags = new String[EVENT_BUFFER_SIZE]; private final int[] mEventJobIds = new int[EVENT_BUFFER_SIZE]; public void addEvent(int cmd, int uid, String tag) { public void addEvent(int cmd, int uid, String tag, int jobId) { int index = mEventIndices.add(); mEventCmds[index] = cmd; mEventTimes[index] = SystemClock.elapsedRealtime(); mEventUids[index] = uid; mEventTags[index] = tag; mEventJobIds[index] = jobId; } DataSet mCurDataSet = new DataSet(); Loading Loading @@ -365,7 +369,8 @@ public final class JobPackageTracker { } else { mCurDataSet.incActive(job.getSourceUid(), job.getSourcePackageName(), now); } addEvent(EVENT_START_JOB, job.getSourceUid(), job.getBatteryName()); addEvent(job.getJob().isPeriodic() ? EVENT_START_PERIODIC_JOB : EVENT_START_JOB, job.getSourceUid(), job.getBatteryName(), job.getJobId()); } public void noteInactive(JobStatus job) { Loading @@ -376,7 +381,8 @@ public final class JobPackageTracker { mCurDataSet.decActive(job.getSourceUid(), job.getSourcePackageName(), now); } rebatchIfNeeded(now); addEvent(EVENT_STOP_JOB, job.getSourceUid(), job.getBatteryName()); addEvent(job.getJob().isPeriodic() ? EVENT_STOP_JOB : EVENT_STOP_PERIODIC_JOB, job.getSourceUid(), job.getBatteryName(), job.getJobId()); } public void noteConcurrency(int totalActive, int fgActive) { Loading Loading @@ -450,14 +456,18 @@ public final class JobPackageTracker { switch (mEventCmds[index]) { case EVENT_START_JOB: label = " START"; break; case EVENT_STOP_JOB: label = " STOP"; break; case EVENT_START_PERIODIC_JOB: label = "START-P"; break; case EVENT_STOP_PERIODIC_JOB: label = " STOP-P"; break; default: label = " ??"; break; } pw.print(prefix); TimeUtils.formatDuration(mEventTimes[index]-now, pw, TimeUtils.HUNDRED_DAY_FIELD_LEN); pw.print(" "); pw.print(label); pw.print(": "); pw.print(": #"); UserHandle.formatUid(pw, uid); pw.print("/"); pw.print(mEventJobIds[index]); pw.print(" "); pw.println(mEventTags[index]); } Loading services/core/java/com/android/server/job/JobSchedulerService.java +5 −2 Original line number Diff line number Diff line Loading @@ -1122,7 +1122,8 @@ public final class JobSchedulerService extends com.android.server.SystemService delayMillis = Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS); JobStatus newJob = new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis, JobStatus.NO_LATEST_RUNTIME, backoffAttempts); JobStatus.NO_LATEST_RUNTIME, backoffAttempts, failureToReschedule.getLastSuccessfulRunTime(), System.currentTimeMillis()); for (int ic=0; ic<mControllers.size(); ic++) { StateController controller = mControllers.get(ic); controller.rescheduleForFailureLocked(newJob, failureToReschedule); Loading Loading @@ -1160,7 +1161,9 @@ public final class JobSchedulerService extends com.android.server.SystemService newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s"); } return new JobStatus(periodicToReschedule, newEarliestRunTimeElapsed, newLatestRuntimeElapsed, 0 /* backoffAttempt */); newLatestRuntimeElapsed, 0 /* backoffAttempt */, System.currentTimeMillis() /* lastSuccessfulRunTime */, periodicToReschedule.getLastFailedRunTime()); } // JobCompletedListener implementations. Loading services/core/java/com/android/server/job/JobStore.java +15 −1 Original line number Diff line number Diff line Loading @@ -345,6 +345,11 @@ public final class JobStore { out.attribute(null, "uid", Integer.toString(jobStatus.getUid())); out.attribute(null, "priority", String.valueOf(jobStatus.getPriority())); out.attribute(null, "flags", String.valueOf(jobStatus.getFlags())); out.attribute(null, "lastSuccessfulRunTime", String.valueOf(jobStatus.getLastSuccessfulRunTime())); out.attribute(null, "lastFailedRunTime", String.valueOf(jobStatus.getLastFailedRunTime())); } private void writeBundleToXml(PersistableBundle extras, XmlSerializer out) Loading Loading @@ -555,6 +560,8 @@ public final class JobStore { IOException { JobInfo.Builder jobBuilder; int uid, sourceUserId; long lastSuccessfulRunTime; long lastFailedRunTime; // Read out job identifier attributes and priority. try { Loading @@ -572,6 +579,12 @@ public final class JobStore { } val = parser.getAttributeValue(null, "sourceUserId"); sourceUserId = val == null ? -1 : Integer.parseInt(val); val = parser.getAttributeValue(null, "lastSuccessfulRunTime"); lastSuccessfulRunTime = val == null ? 0 : Long.parseLong(val); val = parser.getAttributeValue(null, "lastFailedRunTime"); lastFailedRunTime = val == null ? 0 : Long.parseLong(val); } catch (NumberFormatException e) { Slog.e(TAG, "Error parsing job's required fields, skipping"); return null; Loading Loading @@ -708,7 +721,8 @@ public final class JobStore { // And now we're done JobStatus js = new JobStatus( jobBuilder.build(), uid, sourcePackageName, sourceUserId, sourceTag, elapsedRuntimes.first, elapsedRuntimes.second); elapsedRuntimes.first, elapsedRuntimes.second, lastSuccessfulRunTime, lastFailedRunTime); return js; } Loading services/core/java/com/android/server/job/controllers/JobStatus.java +48 −7 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import android.net.Uri; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.text.format.Time; import android.util.ArraySet; import android.util.Slog; import android.util.TimeUtils; Loading Loading @@ -183,6 +184,17 @@ public final class JobStatus { public long madePending; public long madeActive; /** * Last time a job finished successfully for a periodic job, in the currentTimeMillis time, * for dumpsys. */ private long mLastSuccessfulRunTime; /** * Last time a job finished unsuccessfully, in the currentTimeMillis time, for dumpsys. */ private long mLastFailedRunTime; /** * For use only by ContentObserverController: state it is maintaining about content URIs * being observed. Loading @@ -196,7 +208,7 @@ public final class JobStatus { private JobStatus(JobInfo job, int callingUid, String sourcePackageName, int sourceUserId, String tag, int numFailures, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) { long latestRunTimeElapsedMillis, long lastSuccessfulRunTime, long lastFailedRunTime) { this.job = job; this.callingUid = callingUid; Loading Loading @@ -263,6 +275,9 @@ public final class JobStatus { requiredConstraints |= CONSTRAINT_CONTENT_TRIGGER; } this.requiredConstraints = requiredConstraints; mLastSuccessfulRunTime = lastSuccessfulRunTime; mLastFailedRunTime = lastFailedRunTime; } /** Copy constructor. */ Loading @@ -270,7 +285,8 @@ public final class JobStatus { this(jobStatus.getJob(), jobStatus.getUid(), jobStatus.getSourcePackageName(), jobStatus.getSourceUserId(), jobStatus.getSourceTag(), jobStatus.getNumFailures(), jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed()); jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed(), jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime()); } /** Loading @@ -281,18 +297,22 @@ public final class JobStatus { * We consider a freshly loaded job to no longer be in back-off. */ public JobStatus(JobInfo job, int callingUid, String sourcePackageName, int sourceUserId, String sourceTag, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) { String sourceTag, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis, long lastSuccessfulRunTime, long lastFailedRunTime) { this(job, callingUid, sourcePackageName, sourceUserId, sourceTag, 0, earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis); earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, lastSuccessfulRunTime, lastFailedRunTime); } /** Create a new job to be rescheduled with the provided parameters. */ public JobStatus(JobStatus rescheduling, long newEarliestRuntimeElapsedMillis, long newLatestRuntimeElapsedMillis, int backoffAttempt) { long newLatestRuntimeElapsedMillis, int backoffAttempt, long lastSuccessfulRunTime, long lastFailedRunTime) { this(rescheduling.job, rescheduling.getUid(), rescheduling.getSourcePackageName(), rescheduling.getSourceUserId(), rescheduling.getSourceTag(), backoffAttempt, newEarliestRuntimeElapsedMillis, newLatestRuntimeElapsedMillis); newLatestRuntimeElapsedMillis, lastSuccessfulRunTime, lastFailedRunTime); } /** Loading @@ -316,7 +336,8 @@ public final class JobStatus { elapsedNow + job.getMaxExecutionDelayMillis() : NO_LATEST_RUNTIME; } return new JobStatus(job, callingUid, sourcePackageName, sourceUserId, tag, 0, earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis); earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, 0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */); } public void enqueueWorkLocked(IActivityManager am, JobWorkItem work) { Loading Loading @@ -669,6 +690,14 @@ public final class JobStatus { trackingControllers |= which; } public long getLastSuccessfulRunTime() { return mLastSuccessfulRunTime; } public long getLastFailedRunTime() { return mLastFailedRunTime; } public boolean shouldDump(int filterUid) { return filterUid == -1 || UserHandle.getAppId(getUid()) == filterUid || UserHandle.getAppId(getSourceUid()) == filterUid; Loading Loading @@ -1041,5 +1070,17 @@ public final class JobStatus { if (numFailures != 0) { pw.print(prefix); pw.print("Num failures: "); pw.println(numFailures); } final Time t = new Time(); final String format = "%Y-%m-%d %H:%M:%S"; if (mLastSuccessfulRunTime != 0) { pw.print(prefix); pw.print("Last successful run: "); t.set(mLastSuccessfulRunTime); pw.println(t.format(format)); } if (mLastFailedRunTime != 0) { pw.print(prefix); pw.print("Last failed run: "); t.set(mLastFailedRunTime); pw.println(t.format(format)); } } } services/tests/servicestests/src/com/android/server/job/JobStoreTest.java +2 −1 Original line number Diff line number Diff line Loading @@ -206,7 +206,8 @@ public class JobStoreTest extends AndroidTestCase { invalidLateRuntimeElapsedMillis - TWO_HOURS; // Early is (late - period). final JobStatus js = new JobStatus(b.build(), SOME_UID, "somePackage", 0 /* sourceUserId */, "someTag", invalidEarlyRuntimeElapsedMillis, invalidLateRuntimeElapsedMillis); invalidEarlyRuntimeElapsedMillis, invalidLateRuntimeElapsedMillis, 0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */); mTaskStoreUnderTest.add(js); Thread.sleep(IO_WAIT); Loading Loading
services/core/java/com/android/server/job/JobPackageTracker.java +17 −7 Original line number Diff line number Diff line Loading @@ -39,19 +39,23 @@ public final class JobPackageTracker { public static final int EVENT_NULL = 0; public static final int EVENT_START_JOB = 1; public static final int EVENT_STOP_JOB = 2; public static final int EVENT_START_PERIODIC_JOB = 3; public static final int EVENT_STOP_PERIODIC_JOB = 4; private final RingBufferIndices mEventIndices = new RingBufferIndices(EVENT_BUFFER_SIZE); private final int[] mEventCmds = new int[EVENT_BUFFER_SIZE]; private final long[] mEventTimes = new long[EVENT_BUFFER_SIZE]; private final int[] mEventUids = new int[EVENT_BUFFER_SIZE]; private final String[] mEventTags = new String[EVENT_BUFFER_SIZE]; private final int[] mEventJobIds = new int[EVENT_BUFFER_SIZE]; public void addEvent(int cmd, int uid, String tag) { public void addEvent(int cmd, int uid, String tag, int jobId) { int index = mEventIndices.add(); mEventCmds[index] = cmd; mEventTimes[index] = SystemClock.elapsedRealtime(); mEventUids[index] = uid; mEventTags[index] = tag; mEventJobIds[index] = jobId; } DataSet mCurDataSet = new DataSet(); Loading Loading @@ -365,7 +369,8 @@ public final class JobPackageTracker { } else { mCurDataSet.incActive(job.getSourceUid(), job.getSourcePackageName(), now); } addEvent(EVENT_START_JOB, job.getSourceUid(), job.getBatteryName()); addEvent(job.getJob().isPeriodic() ? EVENT_START_PERIODIC_JOB : EVENT_START_JOB, job.getSourceUid(), job.getBatteryName(), job.getJobId()); } public void noteInactive(JobStatus job) { Loading @@ -376,7 +381,8 @@ public final class JobPackageTracker { mCurDataSet.decActive(job.getSourceUid(), job.getSourcePackageName(), now); } rebatchIfNeeded(now); addEvent(EVENT_STOP_JOB, job.getSourceUid(), job.getBatteryName()); addEvent(job.getJob().isPeriodic() ? EVENT_STOP_JOB : EVENT_STOP_PERIODIC_JOB, job.getSourceUid(), job.getBatteryName(), job.getJobId()); } public void noteConcurrency(int totalActive, int fgActive) { Loading Loading @@ -450,14 +456,18 @@ public final class JobPackageTracker { switch (mEventCmds[index]) { case EVENT_START_JOB: label = " START"; break; case EVENT_STOP_JOB: label = " STOP"; break; case EVENT_START_PERIODIC_JOB: label = "START-P"; break; case EVENT_STOP_PERIODIC_JOB: label = " STOP-P"; break; default: label = " ??"; break; } pw.print(prefix); TimeUtils.formatDuration(mEventTimes[index]-now, pw, TimeUtils.HUNDRED_DAY_FIELD_LEN); pw.print(" "); pw.print(label); pw.print(": "); pw.print(": #"); UserHandle.formatUid(pw, uid); pw.print("/"); pw.print(mEventJobIds[index]); pw.print(" "); pw.println(mEventTags[index]); } Loading
services/core/java/com/android/server/job/JobSchedulerService.java +5 −2 Original line number Diff line number Diff line Loading @@ -1122,7 +1122,8 @@ public final class JobSchedulerService extends com.android.server.SystemService delayMillis = Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS); JobStatus newJob = new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis, JobStatus.NO_LATEST_RUNTIME, backoffAttempts); JobStatus.NO_LATEST_RUNTIME, backoffAttempts, failureToReschedule.getLastSuccessfulRunTime(), System.currentTimeMillis()); for (int ic=0; ic<mControllers.size(); ic++) { StateController controller = mControllers.get(ic); controller.rescheduleForFailureLocked(newJob, failureToReschedule); Loading Loading @@ -1160,7 +1161,9 @@ public final class JobSchedulerService extends com.android.server.SystemService newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s"); } return new JobStatus(periodicToReschedule, newEarliestRunTimeElapsed, newLatestRuntimeElapsed, 0 /* backoffAttempt */); newLatestRuntimeElapsed, 0 /* backoffAttempt */, System.currentTimeMillis() /* lastSuccessfulRunTime */, periodicToReschedule.getLastFailedRunTime()); } // JobCompletedListener implementations. Loading
services/core/java/com/android/server/job/JobStore.java +15 −1 Original line number Diff line number Diff line Loading @@ -345,6 +345,11 @@ public final class JobStore { out.attribute(null, "uid", Integer.toString(jobStatus.getUid())); out.attribute(null, "priority", String.valueOf(jobStatus.getPriority())); out.attribute(null, "flags", String.valueOf(jobStatus.getFlags())); out.attribute(null, "lastSuccessfulRunTime", String.valueOf(jobStatus.getLastSuccessfulRunTime())); out.attribute(null, "lastFailedRunTime", String.valueOf(jobStatus.getLastFailedRunTime())); } private void writeBundleToXml(PersistableBundle extras, XmlSerializer out) Loading Loading @@ -555,6 +560,8 @@ public final class JobStore { IOException { JobInfo.Builder jobBuilder; int uid, sourceUserId; long lastSuccessfulRunTime; long lastFailedRunTime; // Read out job identifier attributes and priority. try { Loading @@ -572,6 +579,12 @@ public final class JobStore { } val = parser.getAttributeValue(null, "sourceUserId"); sourceUserId = val == null ? -1 : Integer.parseInt(val); val = parser.getAttributeValue(null, "lastSuccessfulRunTime"); lastSuccessfulRunTime = val == null ? 0 : Long.parseLong(val); val = parser.getAttributeValue(null, "lastFailedRunTime"); lastFailedRunTime = val == null ? 0 : Long.parseLong(val); } catch (NumberFormatException e) { Slog.e(TAG, "Error parsing job's required fields, skipping"); return null; Loading Loading @@ -708,7 +721,8 @@ public final class JobStore { // And now we're done JobStatus js = new JobStatus( jobBuilder.build(), uid, sourcePackageName, sourceUserId, sourceTag, elapsedRuntimes.first, elapsedRuntimes.second); elapsedRuntimes.first, elapsedRuntimes.second, lastSuccessfulRunTime, lastFailedRunTime); return js; } Loading
services/core/java/com/android/server/job/controllers/JobStatus.java +48 −7 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import android.net.Uri; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.text.format.Time; import android.util.ArraySet; import android.util.Slog; import android.util.TimeUtils; Loading Loading @@ -183,6 +184,17 @@ public final class JobStatus { public long madePending; public long madeActive; /** * Last time a job finished successfully for a periodic job, in the currentTimeMillis time, * for dumpsys. */ private long mLastSuccessfulRunTime; /** * Last time a job finished unsuccessfully, in the currentTimeMillis time, for dumpsys. */ private long mLastFailedRunTime; /** * For use only by ContentObserverController: state it is maintaining about content URIs * being observed. Loading @@ -196,7 +208,7 @@ public final class JobStatus { private JobStatus(JobInfo job, int callingUid, String sourcePackageName, int sourceUserId, String tag, int numFailures, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) { long latestRunTimeElapsedMillis, long lastSuccessfulRunTime, long lastFailedRunTime) { this.job = job; this.callingUid = callingUid; Loading Loading @@ -263,6 +275,9 @@ public final class JobStatus { requiredConstraints |= CONSTRAINT_CONTENT_TRIGGER; } this.requiredConstraints = requiredConstraints; mLastSuccessfulRunTime = lastSuccessfulRunTime; mLastFailedRunTime = lastFailedRunTime; } /** Copy constructor. */ Loading @@ -270,7 +285,8 @@ public final class JobStatus { this(jobStatus.getJob(), jobStatus.getUid(), jobStatus.getSourcePackageName(), jobStatus.getSourceUserId(), jobStatus.getSourceTag(), jobStatus.getNumFailures(), jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed()); jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed(), jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime()); } /** Loading @@ -281,18 +297,22 @@ public final class JobStatus { * We consider a freshly loaded job to no longer be in back-off. */ public JobStatus(JobInfo job, int callingUid, String sourcePackageName, int sourceUserId, String sourceTag, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) { String sourceTag, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis, long lastSuccessfulRunTime, long lastFailedRunTime) { this(job, callingUid, sourcePackageName, sourceUserId, sourceTag, 0, earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis); earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, lastSuccessfulRunTime, lastFailedRunTime); } /** Create a new job to be rescheduled with the provided parameters. */ public JobStatus(JobStatus rescheduling, long newEarliestRuntimeElapsedMillis, long newLatestRuntimeElapsedMillis, int backoffAttempt) { long newLatestRuntimeElapsedMillis, int backoffAttempt, long lastSuccessfulRunTime, long lastFailedRunTime) { this(rescheduling.job, rescheduling.getUid(), rescheduling.getSourcePackageName(), rescheduling.getSourceUserId(), rescheduling.getSourceTag(), backoffAttempt, newEarliestRuntimeElapsedMillis, newLatestRuntimeElapsedMillis); newLatestRuntimeElapsedMillis, lastSuccessfulRunTime, lastFailedRunTime); } /** Loading @@ -316,7 +336,8 @@ public final class JobStatus { elapsedNow + job.getMaxExecutionDelayMillis() : NO_LATEST_RUNTIME; } return new JobStatus(job, callingUid, sourcePackageName, sourceUserId, tag, 0, earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis); earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, 0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */); } public void enqueueWorkLocked(IActivityManager am, JobWorkItem work) { Loading Loading @@ -669,6 +690,14 @@ public final class JobStatus { trackingControllers |= which; } public long getLastSuccessfulRunTime() { return mLastSuccessfulRunTime; } public long getLastFailedRunTime() { return mLastFailedRunTime; } public boolean shouldDump(int filterUid) { return filterUid == -1 || UserHandle.getAppId(getUid()) == filterUid || UserHandle.getAppId(getSourceUid()) == filterUid; Loading Loading @@ -1041,5 +1070,17 @@ public final class JobStatus { if (numFailures != 0) { pw.print(prefix); pw.print("Num failures: "); pw.println(numFailures); } final Time t = new Time(); final String format = "%Y-%m-%d %H:%M:%S"; if (mLastSuccessfulRunTime != 0) { pw.print(prefix); pw.print("Last successful run: "); t.set(mLastSuccessfulRunTime); pw.println(t.format(format)); } if (mLastFailedRunTime != 0) { pw.print(prefix); pw.print("Last failed run: "); t.set(mLastFailedRunTime); pw.println(t.format(format)); } } }
services/tests/servicestests/src/com/android/server/job/JobStoreTest.java +2 −1 Original line number Diff line number Diff line Loading @@ -206,7 +206,8 @@ public class JobStoreTest extends AndroidTestCase { invalidLateRuntimeElapsedMillis - TWO_HOURS; // Early is (late - period). final JobStatus js = new JobStatus(b.build(), SOME_UID, "somePackage", 0 /* sourceUserId */, "someTag", invalidEarlyRuntimeElapsedMillis, invalidLateRuntimeElapsedMillis); invalidEarlyRuntimeElapsedMillis, invalidLateRuntimeElapsedMillis, 0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */); mTaskStoreUnderTest.add(js); Thread.sleep(IO_WAIT); Loading