Sorry for my English :)
I have no idea how configure scanner to work properly in background (using ScanJob). I noticed that if the ScanJob starts more than 15 minutes after the previous scan is finished, a passive scan occurs even though there are beacons nearby. The reason for this is that the max age of the region is set to 15 minutes and the region is not restored after ScanJob starts. For now, I do so that after the scanner returns the results, I check if the list from monitoring regions is not empty and if it is, I do
if(beaconManager.monitoredRegions.isEmpty()) {
beaconManager.startRangingBeacons (region)
beaconManager.startMonitoring (region)
}
to set the region again. If I do not do this, passive scan starts every ScanJob stops .
If I invoke
beaconManager.startRangingBeacons (region)
beaconManager.startMonitoring (region)
each time if application starts then ScanJob is canceled immediately.
I wonder if there is any pattern to the background scan setup?
Maybe just remove condition in MonitoringStatus class?
if (millisSinceLastMonitor> MAX_STATUS_PRESERVATION_FILE_AGE_TO_RESTORE_SECS * 1000) {
LogManager.d (TAG, "Not restoring monitoring state because it was recorded too many milliseconds ago:" + millisSinceLastMonitor);
}
The scan job strategy is the default way the Android Beacon Library operates. It uses Android scheduled jobs to schedule scans and requires no configuration:
val beaconManager = BeaconManager.getInstanceForApplication(this)
val region = Region("all-beacons-region", null, null, beaconManager.startMonitoring(region)
beaconManager.startRangingBeacons(region)
In the foreground, scanning is constant and ranging callbacks come every ~1 sec.
In the background, scans are scheduled only once every ~15 minutes due to Android 8+ job scheduling limitations, so ranging callbacks come at that frequency. It is not designed for constant background scanning.
For constant background scanning you may configure the library to use a foreground service or the intent scan strategy.
I've wrote an simple application.
App: Aplication looks like this:
class App: Application(), MonitorNotifier {
val beaconManager by lazy { BeaconManager.getInstanceForApplication(this) }
val parser: BeaconParser =
BeaconParser().setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24")
override fun onCreate() {
BeaconManager.setDebug(true)
super.onCreate()
beaconManager.beaconParsers.add(parser)
beaconManager.addMonitorNotifier(this)
beaconManager.addRangeNotifier { mutableCollection: MutableCollection<Beacon>, region: Region ->
}
}
override fun didEnterRegion(region: Region?) {
}
override fun didExitRegion(region: Region?) {
}
override fun didDetermineStateForRegion(state: Int, region: Region?) {
}
}
MainActivity looks like this:
class MainActivity : AppCompatActivity() {
val region = Region("all-beacons-region", null, null, null)
val beaconManager by lazy { BeaconManager.getInstanceForApplication(this) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onResume() {
super.onResume()
beaconManager.startRangingBeacons(region)
beaconManager.startMonitoring(region)
}
}
in this app configuration, two cases are possible with the assumption that there are beacons nearby
case 1. ScanJob has been started up to 15 minutes after the last scan. After finishing work, you can see in the logs
ScanJob: Checking to see if we need to start a passive scan
ScanJob: We are inside a beacon region. We will not scan between cycles.
and that is correct behavior
case 2. ScanJob has been started more than 15 minutes after the last scan. After finishing work, you will see in the logs:
ScanJob: Checking to see if we need to start a passive scan
it means, the passive scan has started. After a while, StartupBroadcastReceiver is triggered with the results of the passive scan. ScanJob starts to process the passive scan results. After finishing work, you will see in the logs:
ScanJob: Checking to see if we need to start a passive scan
it means, the passive scan has been started again and after a while the StartupBroadcastRetuver will be triggered again with the results of the passive scan. And so it will be over and over again for a while
when you start MainActivity again and call
beaconManager.startRangingBeacons(region)
beaconManager.startMonitoring(region)
In my opinion it is caused by the fact that monitoring state is not restored after 15 minutes from the last scan in the MonitoringStatus class (condition below)
else if (millisSinceLastMonitor > MAX_STATUS_PRESERVATION_FILE_AGE_TO_RESTORE_SECS * 1000) {
LogManager.d(TAG, "Not restoring monitoring state because it was recorded too many milliseconds ago: "+millisSinceLastMonitor);
}
and after the scan is complete, the method is called
private void startPassiveScanIfNeeded() {
if (mScanState != null) {
LogManager.d(TAG, "Checking to see if we need to start a passive scan");
boolean insideAnyRegion = mScanState.getMonitoringStatus().insideAnyRegion();
if (insideAnyRegion) {
// TODO: Set up a scan filter for not detecting a beacon pattern
LogManager.i(TAG, "We are inside a beacon region. We will not scan between cycles.");
}
else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (mScanHelper != null) {
mScanHelper.startAndroidOBackgroundScan(mScanState.getBeaconParsers());
}
}
else {
LogManager.d(TAG, "This is not Android O. No scanning between cycles when using ScanJob");
}
}
}
}
and insideAnyRegion is false because the monitoring state has not been restored, so passive scanning is started even though the beacons are nearby.
To get this case up pretty quickly, I suggest you set MAX_STATUS_PRESERVATION_FILE_AGE_TO_RESTORE_SECS = 1, then when ScanJob start for the first time, you will see what I mean.
The operating system does not always start ScanJob after 15 minutes
protected void updateMonitoringStatusTime(long time) {
File file = mContext.getFileStreamPath(STATUS_PRESERVATION_FILE_NAME);
file.setLastModified(time);
restoreMonitoringStatusIfNeeded();
}
private void restoreMonitoringStatusIfNeeded() {
if(mRegionsStatesMap.isEmpty()){
restoreMonitoringStatus();
}
}
Related
Can't find an answer on stackOverflow, nor in any documentation,
I have the following change stream code(listen to a DB not a specific collection)
Mongo Version is 4.2
#Configuration
public class DatabaseChangeStreamListener {
//Constructor, fields etc...
#PostConstruct
public void initialize() {
MessageListenerContainer container = new DefaultMessageListenerContainer(mongoTemplate, new SimpleAsyncTaskExecutor(), this::onException);
ChangeStreamRequest.ChangeStreamRequestOptions options =
new ChangeStreamRequest.ChangeStreamRequestOptions(mongoTemplate.getDb().getName(), null, buildChangeStreamOptions());
container.register(new ChangeStreamRequest<>(this::onDatabaseChangedEvent, options), Document.class);
container.start();
}
private ChangeStreamOptions buildChangeStreamOptions() {
return ChangeStreamOptions.builder()
.returnFullDocumentOnUpdate()
.filter(newAggregation(match(where(OPERATION_TYPE).in(INSERT.getValue(), UPDATE.getValue(), REPLACE.getValue(), DELETE.getValue()))))
.resumeAt(Instant.now().minusSeconds(1))
.build();
}
//more code
}
I want the stream to start listening from system initiation time only, without taking anything prior in the op-log, will .resumeAt(Instant.now().minusSeconds(1)) work?
do I need to use starAfter method if so how can I found the latest resumeToken in the db?
or is it ready out of the box and I don't need to add any resume/start lines?
second question, I never stop the container(it should always live while app is running), In case of disconnection from the mongoDB and reconnection will the listener in current configuration continue to consume messages? (I am having a hard time simulation DB disconnection)
If it will not resume handling events, what do I need to change in the configuration so that the change stream will continue and will take all the event from the last received resumeToken prior to the disconnection?
I have read this great article on medium change stream in prodcution,
but it uses the cursor directly, and I want to use the spring DefaultMessageListenerContainer, as it is much more elegant.
So I will answer my own(some more dumb, some less :)...) questions:
when no resumeAt timestamp provided the change stream will start from current time, and will not draw any previous events.
resumeAfter event vs timestamp difference can be found here: stackOverflow answer
but keep in mind, that for timestamp it is inclusive of the event, so if you want to start from next event(in java) do:
private BsonTimestamp getNextEventTimestamp(BsonTimestamp timestamp) {
return new BsonTimestamp(timestamp.getValue() + 1);
}
In case of internet disconnection the change stream will not resume,
as such I recommend to take following approach in case of error:
private void onException() {
ScheduledExecutorService executorService = newSingleThreadScheduledExecutor();
executorService.scheduleAtFixedRate(() -> recreateChangeStream(executorService), 0, 1, TimeUnit.SECONDS);
}
private void recreateChangeStream(ScheduledExecutorService executorService) {
try {
mongoTemplate.getDb().runCommand(new BasicDBObject("ping", "1"));
container.stop();
startNewContainer();
executorService.shutdown();
} catch (Exception ignored) {
}
}
First I am creating a runnable scheduled task that always runs(but only 1 at a time newSingleThreadScheduledExecutor()), I am trying to ping the DB, after a successful ping I am stopping the old container and starting a new one, you can also pass the last timestamp you took so that you can get all events you might have missed
timestamp retrieval from event:
BsonTimestamp resumeAtTimestamp = changeStreamDocument.getClusterTime();
then I am shutting down the task.
also make sure the resumeAtTimestamp exist in oplog...
My situation is the following:
I developed a game, where people can save their progress via google cloud. I'm releasing big content-updates, resulting in many people returning to my game at the same time, trying to get their savegame.
This overload causing my customers to get stucked in the savegame-loading process - not able to start playing with their progress.
Here's an updated 4 days screenshot of the cloud-api-dashboard
(And here's the old "12 hours" screenshot of the cloud-api-dashboard)
more informations about the Project:
The game keeps using the "save in cloud"-function in the background on some stages of the game to provide players with the functionality to play on two diffrent devices.
I'm using Unity 2019.3.9f1 and the Asset "Easy Mobile Pro 2.17.3" for the Game-Service-Feature.
The "Google Play Games Plugin" has the version "0.10.12" and can be found on github
more informations about the Cloud-Situation:
The OAuth "user type" is "External" (and can't be changed)
The OAuth user cap display shows "0/100" for the user-cap
And The OAuth rate limits is displaying this for the token-grant-rate (highest "Average Token Grant Rate" is 3,33 of 10.000 as limit)
All used quotas are within the limit. The project reaches
1/10 of "queries per day" (1.000.000.000 max) and
1/2 of "queries per 100 sec" (20.000 max).
more informations about the Error-Trace in the cloud-API:
On my search for a better Error-Log I tried to find “Cloud Logging”-tools in the “Google Cloud Platform”-console. But every section i tried won’t display anything:
“Logging” (Operations tool) is empty
“Cloud Logging API” says: “no data available for the selected time frame.”
“Cloud Debugger API” says: “no data available for the selected time frame.”
I can't find a more detailed variant of the errors as this (the "Metrics"-Section in the "Google Drive API"):
Is there anything I miss to get a better insight?
more informations about the Core-Code
As I mentioned, I’m using “EasyMobilePro”, so I have one “SaveGame”-Var and 8 calls for google and apple as well. I already contacted their support: They assured me that those calls are unchangeable & kind of rock solid (so it can’t be caused from their code) and I should try to contact google if the problem is not on my side.
The 5 calls from EasyMobile for cloudsave are:
bool “GameServices.IsInitialized()”
void “GameServices.OpenWithAutomaticConflictResolution”
void “GameServices.WriteSavedGameData”
void “GameServices.ReadSavedGameData”
void “GameServices.DeleteSavedGame”
The 3 calls from EasyMobile for cloud-login are:
void “GameServices.Init()”
delegate “GameServices.UserLoginSucceeded”
delegate “GameServices.UserLoginFailed”
The Process, that causes the Issue:
I call “GameService.Init()”, the user logs in (no problem)
On that “LoginSuccess”-Callback I call my Function “HandleFirstCloudOpening”:
//This Method is Called, after the player Pressed "Save/ Load" on the StartScreen
//The button is disabled imidiately (and will be re-enabled if an error/fail happens)
public void TryCallUserLogin() {
if (!IsLoginInit) {
EasyMobile.GameServices.UserLoginFailed += HandleLoginFail;
EasyMobile.GameServices.UserLoginSucceeded += HandleFirstCloudOpening;
IsLoginInit = true;
}
if (!IsGameServiceInitialized) {
EasyMobile.GameServices.Init();
} else { //This "else" is only be called, if the "Init" was successfull, but the player don't have a connected savegame
HandleFirstCloudOpening();
}
}
private void HandleLoginFail() {
//(...) Show ErrorPopup, let the player try to login again
}
private void HandleFirstCloudOpening() {
if (currentSaveState != CloudSaveState.NONE) {
CloudStateConflictDebug(CloudSaveState.OPENING);
return;
}
currentSaveState = CloudSaveState.OPENING;
EasyMobile.GameServices.SavedGames.OpenWithAutomaticConflictResolution(cloudSaveNameReference, UseFirstTimeOpenedSavedGame);
}
private void UseFirstTimeOpenedSavedGame(EasyMobile.SavedGame _savedGame, string _error) {
currentSaveState = CloudSaveState.NONE;
if (string.IsNullOrEmpty(_error)) {
cloudSaveGame = _savedGame;
ReadDataFromCloud(cloudSaveGame);
} else {
ErrorPopupWithCloseButton("cloud_open", "failed with error: " + _error);
}
}
private void ReadDataFromCloud(EasyMobile.SavedGame _savedGame) {
if (_savedGame.IsOpen) {
currentSaveState = CloudSaveState.LOADING;
EasyMobile.GameServices.SavedGames.ReadSavedGameData(_savedGame, UseSucessfullLoadedCloudSaveGame);
} else { //backup function if the fresh-opened savegame is "closed" for some reason (can happen later while "saving" ingame)
HandleFirstCloudOpening();
}
}
private void UseSucessfullLoadedCloudSaveGame(EasyMobile.SavedGame _game, byte[] _cloudData, string error) {
if (!string.IsNullOrEmpty(error)) {
ErrorPopupWithCloseButton("cloud_read", "Reading saved game data failed: " + error);
return;
}
if (_cloudData.Length > 0) {
//A function, that converts the saved bytes to my useable Savegame-Data
//having a "try&catch": if it fails, it useses the callback with the param "null"
SaveGameToByteConverter.LoadFromBytes<CoreSaveData>(_cloudData, UseSucessfullConvertedSavegameData);
} else {
//this will "fail", causing the use of the callback with the param "null"
SaveGameToByteConverter.LoadFromBytes<CoreSaveData>(null, UseSucessfullConvertedSavegameData);
}
}
private void UseSucessfullConvertedSavegameData(CoreSaveData _convertedSaveGame) {
//Has a Loaded & normal SaveGame in his cloud
if (_convertedSaveGame != null) {
//Loaded Save matches verify-conditions
if (CheckLoadedSaveIsVerified(_convertedSaveGame)) {
OverrideGameSaveDatawithLoaded(_convertedSaveGame);
ReloadCurrentScene();
return;
} else { //This happens if the cloud-save doesn't pass my verification-process
ErrorPopupWithCloseButton("cloud_loadedSave", "Couldn't find a compatible Savegame!");
return;
}
} else { //User uses Cloud-save for the frist Time or has an unusable savegame and gets a "new" (lost his old data)
TrySaveGameToCloud((bool _saved) => {
SaveAllGameFilesLocally();
});
}
}
I shrunk the code by removing most of my “if error happens, do XY”, since there are many and they would extend the reprex. If necessary I can provide a more detailed (but more complicated) code.
current conclusion
I can't find any issue on my side, that wouldn't have been fixed with a "restart of the game" or woudln't been covered by an error-popup for the user. It's like they are queued because of the amount of users and need to wait way too long for a response. Some users told us they had to wait & tried "x hours" (it's variable from 2h to 36h) and then they passed to play with their progress (so it worked). But some players mentioned they couldn't play again on the next day (same problem). Like their "access-token" only holds for a day?
Edit-History:
(1) updated the first dash-board-picture to match the ongoing situation
(1) added "more informations about the cloud-situation"
(1) can't find a more detailed error-log
(2) removed most pictures as displayables (kept the links)
(2) added "more informations about the Error-Trace in the cloud-API"
(2) added "more informations about the Core-Code" and a Reprex
(2) added "current conclusion"
How can I process a list of delayed jobs in Vertx (actually
hundreds of HTTP GET requests, to limited API that bans fast requesting hosts)? now, I am using this code and it gets blocked because Vertx starts all requests at once. It is desirable to process each request with a 5-second delay between each request.
public void getInstrumnetDailyInfo(Instrument instrument,
Handler<AsyncResult<OptionInstrument>> handler) {
webClient
.get("/Loader")
.addQueryParam("i", instrument.getId())
.timeout(30000)
.send(
ar -> {
if (ar.succeeded()) {
String html = ar.result().bodyAsString();
Integer thatData = processHTML(html);
instrument.setThatData(thatData);
handler.handle(Future.succeededFuture(instrument));
} else {
// error
handler.handle(Future.failedFuture("error " +ar.cause()));
}
});
}
public void start(){
List<Instrument> instruments = loadInstrumentsList();
instruments.forEach(
instrument -> {
webClient.getInstrumnetDailyInfo(instrument,
async -> {
if(async.succeeded()){
instrumentMap.put(instrument.getId(), instrument);
}else {
log.warn("getInstrumnetDailyInfo: ", async.cause());
}
});
});
}
You can consider using a timer to fire events (rather than all at startup).
There are two variants in Vertx,
.setTimer() that fires a specific event after a delay
vertx.setTimer(interval, new Handler<T>() {});
and
2. .setPeriodic() that fires every time a specified period of time has passed.
vertx.setPeriodic(interval, new Handler<Long>() {});
setPeriodic seems to be what you are looking for.
You can get more info from the documentation
For more sophisticated Vertx scheduling use-cases, you can have a look at Chime or other schedulers or this module
You could use any out of the box rate limiter function and adapt it for async use.
An example with the RateLimiter from Guava:
// Make permits available at a rate of one every 5 seconds
private RateLimiter limiter = RateLimiter.create(1 / 5.0);
// A vert.x future that completes when it obtains a throttle permit
public Future<Double> throttle() {
return vertx.executeBlocking(p -> p.complete(limiter.acquire()), true);
}
Then...
throttle()
.compose(d -> {
System.out.printf("Waited %.2f before running job\n", d);
return runJob(); // runJob returns a Future result
});
I am trying to code around the fact that the messageLostHandler doesn't fire for many minutes after a device is out of range using Audio (or Earshot for Android).
I was hoping that every few secs a message would be received from another device. It fires once. Is this expected? Since I can't rely on the messageLost handler - how do I know when a device is truly out of range of the ultrasonic?
I coded up a timer after receiving the subscriptionWithMessageFoundHandler hoping another message coming in I could just invalidate or restart the timer. If the timer fired, I'd know that x seconds passed and that the other device must be out of range. No such luck.
UPDATE: Here is the code in question:
let strategy = GNSStrategy.init(paramsBlock: { (params: GNSStrategyParams!) -> Void in
params.discoveryMediums = .Audio
})
publication = messageMgr.publicationWithMessage(pubMessage, paramsBlock: { (pubParams: GNSPublicationParams!) in
pubParams.strategy = strategy
})
subscription = messageMgr.subscriptionWithMessageFoundHandler({[unowned self] (message: GNSMessage!) -> Void in
self.messageViewController.addMessage(String(data: message.content, encoding:NSUTF8StringEncoding))
// We only seem to get a 1x notification of a message. So this timer is folly.
print("PING") //Only 1x per discovery.
}, messageLostHandler: {[unowned self](message: GNSMessage!) -> Void in
self.messageViewController.removeMessage(String(data: message.content, encoding: NSUTF8StringEncoding))
}, paramsBlock: { (subParams: GNSSubscriptionParams!) -> Void in
subParams.strategy = strategy
})
Notice that the "PING" only prints once.
When a device goes out of range, Nearby waits for 2 minutes before flushing the other device's token from its cache. So if you wait for 2 minutes, the messageLost handler should be called. Can you verify this? Also, is it safe to assume that you'd like to have a timeout shorter than 2 minutes? This timeout has been a topic of discussion, and there's been some talk of adding a parameter so apps can choose a value that's more appropriate for its use case.
My app has a long running task (anywhere from 5 minutes to 2 hours) that I can start through an admin panel.
I need a good way to know whether the task is currently running or not, so I can display the status on the admin panel and to prevent the task from being started twice.
Currently my code looks like this (simplified):
object TaskMonitor extends Controller {
var isRunning = false
// Displays the task status on the admin panel
def status = Action {
Ok.chunked(running &> Comet(callback = "parent.running"))
}
// Check task status every 100 ms
lazy val running: Enumerator[String] = {
Enumerator.generateM {
Promise.timeout(Some(isRunning.toString), 100 milliseconds)
}
}
// start the task, but only if it's not already running
def startTask = Action {
if (!isRunning) {
isRunning = true
val f = scala.concurrent.Future { Task.start }
f onComplete {
case _ => isRunning = false
}
}
Ok
}
}
Obviously this is has all kinds of issues, mainly the fact that I have unsynchronized mutable state (isRunning variable) in my controller.
What would be the correct way to go about what I want to achieve ?
You're right, you have unsynchronized mutable state. Is it really a problem? I mean this is your admin right? How many concurrent 'startTask' are you gonna send?
The recommended way to handle this in Play! is to use an Akka actor - you don't need any extra dependency, Play! includes Akka already.
Create an actor to hold your isRunning value; in startTask you can send a message to the actor to start the task (the actor will check if the task is not already running). To send the current status of the task you can query the actor for the value of isRunning.
This will give you a protected mutable state. Your choice to decide if it's really worth the trouble.