Progressive Web App: skipWaiting() with multiple service worker versions - progressive-web-apps

[CONTEXT]
I worked through Jake Archibald's fantastic Udacity course found here: Offline Web Applications. His work provides a Toasts dialog alerting the user that there is an update available, and they are invited to update:
Refresh / Dismiss Dialog
While this dialog is available to the user, there's a corner case on hand that I can't seem to resolve:
The service-worker can be updated any number of times prior to the client updating the local instance, pushing the numbered version of the service worker past 'just one more'. For example, the current and active service worker is #821, while the service worker that is waiting is now #824
active and waiting service workers
[PROBLEM]
I cannot find the right way to alert the browser that the next service worker to install needs to be #824, instead of #822, the dialog-box + PWA tell me that the current browser is 'redundant', and that I can't get to service-worker #824 without refreshing, and then clicking the update button.
I can recreate this with any version of Jake's code once the service-worker is set, and skipWaiting() is introduced.
I literally just want to be able to cover the corner case where the service-worker is updated 2 or more times before the user decides to update their local PWA.
You can find Jake's code on github: Jakearchibald/wittr
[ASK]: Has anyone found a solution for this corner case? If so, how do you solve it? What I'm seeing doesn't make sense as the service-worker lifecycle seems to respected per Googles documentation: service-workers/lifecyle

I did quite a bit of additional reading/research and found the following discussion threads on Github:
- Provide an easier way to listen for waiting/activated/redundant Service Workers
- Immediate Service Worker
- Recommended Approach for Refreshing Page on new SW
- Provide a one-line way to listen for a waiting Service Worker
It looks like this idea was brought up in 2017, and has for the most part gone stale. However, you can double down on using
navigator.serviceWorker.waiting.then(reg => {
if (confirm('refresh now?')) reg.waiting.postMessage('skipWaiting');
});
That will give you the ability to listen for a new Web Worker, after activating Web Worker #1, then setting Web Worker #2 to redundant, and moving Web Worker #3 into a waiting state. It's obtuse and indirect, but at least you can now move the 3# thread up and into the right slot.
A real shout-out to dfabulich, Matt Gaunt, and Beatrix Perez

Related

Which service worker event indicates it has controlled the page, and will intercept web traffic?

In my web app, some web requests must be intercepted and modified by the service worker, otherwise the requests will fail. This is especially important on the very first visit for a new user. I use clientsClaim() to ensure that.
Since I need to make sure the service worker is ready before I make the request, I tried to wait for navigator.serviceWorker.ready:
await navigator.serviceWorker.ready;
fetch(myRequest);
However, I found it doesn't work as intended. The very first request on the first visit is not intercepted. So I tried to add some wait time:
await navigator.serviceWorker.ready;
await twoSeconds();
fetch(myRequest);
This works, but it damages user experience because it delays the first meaningful UI. On the other hand, I also can't be sure 2 seconds is long enough for every computer.
What's the event that can tell me as soon as the the sw is ready to intercept traffic? It's only a problem on the first visit, but ideally the event will fire on every reload, because the code is easier to write if I simply await the same thing on every visit.
I think you're looking for a promise that you can use to signal when the current page is under control of a service worker.
This can be achieved via
await new Promise(r => {
if (navigator.serviceWorker.controller) return r();
navigator.serviceWorker.addEventListener('controllerchange', e => r());
});
// At this point, the page will be controlled by a service worker.
This code is adapted from this GitHub discussion, and there's more context there.
Generally speaking, it's not great to design a page that will only work if it's controlled by a service worker, since service workers are intended to be progressive enhancement, rather than a core requirement. But if you have that use case, the code above will help.

Chrome Dev Tools - Filter out xhrs from background worker

I'm trying to troubleshoot some UI issues that I've been having that I think are tied to XHR requests that I'm making on the main thread. But when I look at the network tab, I see a ton of my background worker requests and it becomes really hard to see exactly which request came from where.
Is there any way to filter the network request so that I can see if the request was initiated from a background worker (and if so, which one) or the main thread?
I added the "initiator" column to my network tab, but it just says "Other".
Thanks in advance for any help

AppFabric Hosted Workflow does not always reload after delay/unload

I have a WCF Windows Workflow (4.5) Workflow Service hosted under IIS and using AppFabric 1.1. The workflow instances are long-running (up to about a week), but much of the time is spent in Delay activities.
This seemed to work fine at first, but when running multiple instances of the workflow at the same time (2+ instances causes this), some of them just never wake up once they've unloaded from memory during the Delay step. When I look at the logs, the errors I find all look like this:
System.OperationCanceledException: The execution of InstancePersistenceCommands has been canceled because the InstanceHandle was freed.
at System.Runtime.AsyncResult.End[TAsyncResult](IAsyncResult result)
at System.ServiceModel.Activities.Dispatcher.DurableInstanceManager.WaitAndHandleStoreEventsCallback(IAsyncResult result)
Unfortunately, I'm not finding any useful information on that error message.
The SuspensionExceptionName and SuspensionReason fields in the AppFabric Persisted Instances Table show System.NullReferenceException: Object reference not set to an instance of an object. But this doesn't happen inside my workflow, only outside.
Additional Info:
I'm running the activity as a Fire & Forget (receive activity, no send)
My workflow calls into other WCF services to fetch data.
I am running this on Server 2012 R2, IIS 8 (not azure)
Workflow Persistence is working. I can reset IIS, reboot... its just when I run 2 instances that it has problems.
I'm definitely not hitting any kind of throttling limits. While the workflow deals with a few MB of data, this issue happens at 2+ instances.
Any idea what might be happening here?
Edit:
I realized I found more information on how the issue operates and never added it to the question. When the delay issue happens, it operates a lot like a static variable getting written by 2 threads.
Here's a visualization:
WF1 Start ---->Do Stuff--->Sleep------------*1----->Cancelled Exception at some point
------WF 2 Start---->Do Stuff------->Sleep->Wake up---------*2------>More Stuff---->End Successfully
*1 - When WF Instance 1 Should Wake up (Same time as WF 2 wakes)
*2 - When WF Instance 2 Should have woken up (Seems to be ignored)
Before anyone asks... I got rid of every static variable, method, class in my code. Nothing is static anymore.
I've been struggling with similar issues for quite a while. I use WFW4 and I find similar errors when a workflow instance is in a long delay.
I don't know what the cause of the problem is, but I have a work around that you might find helpful.
In my case, the errors I get are from Workflow Management Service and say:
Failed to invoke service management endpoint at 'net.pipe://.svc' to activate service '/Alerts/Workflows/.xamlx'. Exception: 'Access is denied.'
These errors start happening sometime between 6 and 30 hours after the instance goes into a long delay.
I have found that if I create a new instance of the workflow when the first instance is in delay and the errors are happening, then Workflow Management Service is able to resume interacting with the first sleeping instance.
So, I made a new workflow whose sole purpose is to periodically launch and then kill instances of the workflow that contains the long delay.
It actually gets a bit more complicated to make this work. I wanted this new workflow to also go to sleep between times when it creates and kills a new instance of the first workflow. But this going to sleep causes the instance of the new workflow to suffer the same problem as the first workflow. So, I modified the new workflow so it does the following:
-- delay for some rather short period, such as 30 minutes
-- create an instance of the first workflow
-- wait a minute
-- kill the just-created instance of the first workflow
-- create a new instance of this new error-preventing workflow
-- terminate
Since having done this, I no longer get the Access is Denied error from Workflow Management Service!
Hope this helps
Turns out my first answer was not correct, but I believe this answer is right, and solves the issue ChrisG is having.
My workaround did not actually work. Took a while for the problem to resurface. 29 hours to be precise - the default time it takes for an app pool to recycle.
So for me, the solution was to make my app pool not recycle. When an app pool recycles while a workflow instance is in a delay activity, the workflowManagementService is not able to wake up the instance and throws Access is Denied errors. If you create a new instance of the workflow after the app pool has recycled, the first instance will pick up where it left off, but sometimes still has problems, which is what I believe is happening to ChrisG.
ChrisG, looking at your visualization, is it possible that an appPool is recycling during the time wf1 is sleeping? I believe that is the cause the exception. If you then launch a new wf instance after *2 has passed (and if an app pool recycle happened prior to *1), that will wake up both wf1 and wf2, but wf1 won't work properly (at least in my experience)
Also, this happens after iisresets and server reboots. To handle those, you need to use IIS7 which allows the web application (as well as the web site) which is hosting the xamlx files to autostart after an iisreset or server reboot. This option is not available in IIS6. See http://www.postseek.com/meta/991815402b369e71ce925cde47ac907d for details
Hope this helps!

Activity not responding error on emulator when using webservices in json parsing?

Am new to android, am developing application with websevices using json parsing with httpget method,cant use http post method in android actually.
It working fine normally, but many time it shows the error on emulator like activity not responding force close activity.when i put that url in browser it shows the result .but i don't know why this activity not responding error came.
I think the httprequest took more time to retrieve the data from server,but am not sure. any one help me to how to avoid this error or how to minimize this .
I want know what are the possibilities to get this activity not responding error.
Thanks,
Lakshmanan
You need to perform blocking operations such as I/O in a separate thread - see the below linked resource:
http://developer.android.com/guide/practices/design/responsiveness.html:
In Android, the system guards against applications that are insufficiently responsive for a period of time by displaying a dialog to the user, called the Application Not Responding (ANR) dialog, shown at right in Figure 1. The user can choose to let the application continue, but the user won't appreciate having to act on this dialog every time he or she uses your application. It's critical to design responsiveness into your application, so that the system never has cause to display an ANR dialog to the user.
To avoid ANR (Application Not Responding) dialog,
Your business logic code is inside doBackground() of AsyncTask and You may also need to override onPostExecute(),etc. After that it is better to invoke the async task in a Service (bound or normal service).
Service:
(bound service or normal service based on your requirement)
From, android office documentation:
A service is "bound" when an application component binds to it by calling bindService(). A bound service offers a client-server interface that allows components to interact with the service, send requests, get results, and even do so across processes with interprocess communication (IPC). A bound service runs only as long as another application component is bound to it. Multiple components can bind to the service at once, but when all of them unbind, the service is destroyed.

How to use a WF DelayActivity in an ASP.Net web based workflow

I have a web application that I am adding workflow functionality to using Windows Workflow Foundation. I have based my solution around K. Scott Allen's Orders Workflow example on OdeToCode. At the start I didn't realise the significance of the caveat "if you use Delay activities with and configure active timers for the manual scheduling service, these events will happen on a background thread that is not associated with an HTTP request". I now need to use Delay activities and it doesn't work as is with his solution architecture. Has anyone come across this and found a good solution to this? The example is linked to from a lot of places but I haven't seen anyone else come across this issue and it seems like a bit of a show stopper to me.
Edit: The problem is that the results from the workflow are returned to the the web application via HttpContext. I am using the ManualWorkflowSchedulerService with the useActiveTimers and this works fine for most situations because workflow events are fired from the web app and HttpContext still exists when the workflow results are returned and the web app can continue processing. When a delay activity is used processing happens on a background thread and when it tries to return results to the web app, there is no valid HttpContext (because there has been no Http Request), so further processing fails. That is, the webapp is trying to process the workflow results but there has been no http request.
I think I need to do all post Delay activity processing within the workflow rather than handing off to the web app.
Cheers.
You didn't describe the problem you are having. But maybe this is of some help.
You can use the ManualWorkflowSchedulerService with the useActiveTimers and the workflow will continue on another thread. Normally this is fine because your HTTP request has already finished and it doesn't really matter.
If however you need full control the workflow runtime will let you get a handle on all loaded workflows using the GetLoadedWorkflows() function. This will return acollection of WorkflowInstance objects. usign these you can can call the GetWorkflowNextTimerExpiration() to check which is expired. If one is you can manually resume it. In this case you want to use the ManualWorkflowSchedulerService with the useActiveTimers=false so you can control the last thread as well. However in most cases using useActiveTimers=true works perfectly well.