Use coroutines in sub services

This commit is contained in:
sictiru 2023-06-04 16:53:31 -07:00
parent b16e124bad
commit b857f8f078
8 changed files with 109 additions and 141 deletions

View file

@ -1,5 +1,6 @@
package com.newsblur.service;
import com.newsblur.util.ExtensionsKt;
import com.newsblur.util.PrefConstants;
import com.newsblur.util.PrefsUtils;
@ -8,7 +9,7 @@ public class CleanupService extends SubService {
public static boolean activelyRunning = false;
public CleanupService(NBSyncService parent) {
super(parent);
super(parent, ExtensionsKt.NBScope);
}
@Override

View file

@ -3,6 +3,7 @@ package com.newsblur.service;
import android.util.Log;
import com.newsblur.util.AppConstants;
import com.newsblur.util.ExtensionsKt;
import com.newsblur.util.PrefsUtils;
import java.util.Collections;
@ -19,7 +20,7 @@ public class ImagePrefetchService extends SubService {
static Set<String> ThumbnailQueue = Collections.synchronizedSet(new HashSet<>());
public ImagePrefetchService(NBSyncService parent) {
super(parent);
super(parent, ExtensionsKt.NBScope);
}
@Override

View file

@ -5,6 +5,7 @@ import static com.newsblur.service.NBSyncReceiver.UPDATE_TEXT;
import com.newsblur.database.DatabaseConstants;
import com.newsblur.network.domain.StoryTextResponse;
import com.newsblur.util.AppConstants;
import com.newsblur.util.ExtensionsKt;
import com.newsblur.util.FeedUtils;
import java.util.HashSet;
@ -22,14 +23,12 @@ public class OriginalTextService extends SubService {
private static final Pattern imgSniff = Pattern.compile("<img[^>]*src=(['\"])((?:(?!\\1).)*)\\1[^>]*>", Pattern.CASE_INSENSITIVE);
/** story hashes we need to fetch (from newly found stories) */
private static Set<String> Hashes;
static {Hashes = new HashSet<String>();}
private static final Set<String> Hashes = new HashSet<>();
/** story hashes we should fetch ASAP (they are waiting on-screen) */
private static Set<String> PriorityHashes;
static {PriorityHashes = new HashSet<String>();}
private static final Set<String> PriorityHashes = new HashSet<>();
public OriginalTextService(NBSyncService parent) {
super(parent);
super(parent, ExtensionsKt.NBScope);
}
@Override

View file

@ -1,129 +0,0 @@
package com.newsblur.service;
import static com.newsblur.service.NBSyncReceiver.UPDATE_STATUS;
import android.os.Process;
import com.newsblur.NbApplication;
import com.newsblur.util.AppConstants;
import com.newsblur.util.Log;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.RejectedExecutionException;
/**
* A utility construct to make NbSyncService a bit more modular by encapsulating sync tasks
* that can be run fully asynchronously from the main sync loop. Like all of the sync service,
* flags and data used by these modules need to be static so that parts of the app without a
* handle to the service object can access them.
*/
public abstract class SubService {
protected NBSyncService parent;
private ThreadPoolExecutor executor;
private long cycleStartTime = 0L;
private SubService() {
; // no default construction
}
SubService(NBSyncService parent) {
this.parent = parent;
executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
public void start() {
Runnable r = new Runnable() {
public void run() {
if (parent.stopSync()) return;
if (!NbApplication.isAppForeground()) {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_LESS_FAVORABLE );
} else {
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT + Process.THREAD_PRIORITY_LESS_FAVORABLE + Process.THREAD_PRIORITY_LESS_FAVORABLE );
}
Thread.currentThread().setName(this.getClass().getName());
exec_();
}
};
try {
executor.execute(r);
// enqueue a check task that will run strictly after the real one, so the callback
// can effectively check queue size to see if there are queued tasks
executor.execute(new Runnable() {
public void run() {
parent.checkCompletion();
parent.sendSyncUpdate(UPDATE_STATUS);
}
});
} catch (RejectedExecutionException ree) {
// this is perfectly normal, as service soft-stop mechanics might have shut down our thread pool
// while peer subservices are still running
}
}
private synchronized void exec_() {
try {
exec();
cycleStartTime = 0;
} catch (Exception e) {
com.newsblur.util.Log.e(this.getClass().getName(), "Sync error.", e);
}
}
protected abstract void exec();
public void shutdown() {
Log.d(this, "SubService stopping");
executor.shutdown();
try {
executor.awaitTermination(AppConstants.SHUTDOWN_SLACK_SECONDS, TimeUnit.SECONDS);
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
} finally {
Log.d(this, "SubService stopped");
}
}
public boolean isRunning() {
// don't advise completion until there are no tasks, or just one check task left
return (executor.getQueue().size() > 0);
}
/**
* If called at the beginning of an expensive loop in a SubService, enforces the maximum duty cycle
* defined in AppConstants by sleeping for a short while so the SubService does not dominate system
* resources.
*/
protected void startExpensiveCycle() {
if (cycleStartTime == 0) {
cycleStartTime = System.nanoTime();
return;
}
double lastCycleTime = (System.nanoTime() - cycleStartTime);
if (lastCycleTime < 1) return;
cycleStartTime = System.nanoTime();
double cooloffTime = lastCycleTime * (1.0 - AppConstants.MAX_BG_DUTY_CYCLE);
if (cooloffTime < 1) return;
long cooloffTimeMs = Math.round(cooloffTime / 1000000.0);
if (cooloffTimeMs > AppConstants.DUTY_CYCLE_BACKOFF_CAP_MILLIS) cooloffTimeMs = AppConstants.DUTY_CYCLE_BACKOFF_CAP_MILLIS;
if (NbApplication.isAppForeground()) {
com.newsblur.util.Log.d(this.getClass().getName(), "Sleeping for : " + cooloffTimeMs + "ms to enforce max duty cycle.");
try {
Thread.sleep(cooloffTimeMs);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}

View file

@ -0,0 +1,96 @@
package com.newsblur.service
import com.newsblur.NbApplication.Companion.isAppForeground
import com.newsblur.util.AppConstants
import com.newsblur.util.Log
import com.newsblur.util.NBScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import java.util.concurrent.CancellationException
/**
* A utility construct to make NbSyncService a bit more modular by encapsulating sync tasks
* that can be run fully asynchronously from the main sync loop. Like all of the sync service,
* flags and data used by these modules need to be static so that parts of the app without a
* handle to the service object can access them.
*/
abstract class SubService(
@JvmField
protected val parent: NBSyncService,
private val coroutineScope: CoroutineScope = NBScope,
) {
private var cycleStartTime = 0L
protected abstract fun exec()
fun start() {
coroutineScope.launch {
if (!parent.stopSync()) {
execInternal()
}
if (isActive) {
parent.checkCompletion()
parent.sendSyncUpdate(NBSyncReceiver.UPDATE_STATUS)
}
}
}
private suspend fun execInternal() = coroutineScope {
try {
ensureActive()
exec()
cycleStartTime = 0
} catch (e: Exception) {
Log.e(this.javaClass.name, "Sync error.", e)
}
}
fun shutdown() {
Log.d(this, "SubService shutdown")
try {
coroutineScope.cancel()
} catch (e: CancellationException) {
Log.d(this, "SubService cancelled")
} finally {
Log.d(this, "SubService stopped")
}
}
// don't advise completion until there are no tasks, or just one check task left
val isRunning: Boolean
get() =// don't advise completion until there are no tasks, or just one check task left
coroutineScope.isActive
/**
* If called at the beginning of an expensive loop in a SubService, enforces the maximum duty cycle
* defined in AppConstants by sleeping for a short while so the SubService does not dominate system
* resources.
*/
protected fun startExpensiveCycle() {
if (cycleStartTime == 0L) {
cycleStartTime = System.nanoTime()
return
}
val lastCycleTime = (System.nanoTime() - cycleStartTime).toDouble()
if (lastCycleTime < 1) return
cycleStartTime = System.nanoTime()
val cooloffTime = lastCycleTime * (1.0 - AppConstants.MAX_BG_DUTY_CYCLE)
if (cooloffTime < 1) return
var cooloffTimeMs = Math.round(cooloffTime / 1000000.0)
if (cooloffTimeMs > AppConstants.DUTY_CYCLE_BACKOFF_CAP_MILLIS) cooloffTimeMs = AppConstants.DUTY_CYCLE_BACKOFF_CAP_MILLIS
if (isAppForeground) {
Log.d(this.javaClass.name, "Sleeping for : " + cooloffTimeMs + "ms to enforce max duty cycle.")
try {
Thread.sleep(cooloffTimeMs)
} catch (e: InterruptedException) {
Thread.currentThread().interrupt()
}
}
}
}

View file

@ -24,8 +24,6 @@ import kotlinx.coroutines.launch
*/
class SubscriptionSyncService : JobService() {
private val scope = NBScope
override fun onStartJob(params: JobParameters?): Boolean {
Log.d(this, "onStartJob")
if (!PrefsUtils.hasCookie(this)) {
@ -33,10 +31,10 @@ class SubscriptionSyncService : JobService() {
return false
}
val subscriptionManager = SubscriptionManagerImpl(this@SubscriptionSyncService, scope)
val subscriptionManager = SubscriptionManagerImpl(this@SubscriptionSyncService)
subscriptionManager.startBillingConnection(object : SubscriptionsListener {
override fun onBillingConnectionReady() {
scope.launch {
NBScope.launch {
subscriptionManager.syncActiveSubscription()
Log.d(this, "sync active subscription completed.")
// manually call jobFinished after work is done

View file

@ -3,6 +3,7 @@ package com.newsblur.service;
import com.newsblur.network.domain.StoriesResponse;
import com.newsblur.network.domain.UnreadStoryHashesResponse;
import com.newsblur.util.AppConstants;
import com.newsblur.util.ExtensionsKt;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.StateFilter;
@ -26,7 +27,7 @@ public class UnreadsService extends SubService {
static { StoryHashQueue = new ArrayList<String>(); }
public UnreadsService(NBSyncService parent) {
super(parent);
super(parent, ExtensionsKt.NBScope);
}
@Override

View file

@ -20,6 +20,7 @@ fun <R> CoroutineScope.executeAsyncTask(
withContext(Dispatchers.Main) { onPostExecute(result) }
}
@JvmField
val NBScope = CoroutineScope(
CoroutineName(TAG) +
Dispatchers.Default +