Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Workers API #532

Open
26 of 36 tasks
Plamen5kov opened this issue Aug 9, 2016 · 45 comments
Open
26 of 36 tasks

Workers API #532

Plamen5kov opened this issue Aug 9, 2016 · 45 comments

Comments

@Plamen5kov
Copy link
Contributor

Plamen5kov commented Aug 9, 2016

Support for Workers

For iOS solution please check - NativeScript/ios-jsc#620

Description

General guidelines for the Workers implementation effort and specification in the context of NativeScript.
We have an existing issue here, but the purpose here is to show the road map that the team intends to follow in developing the Workers functionality. The general scenarios we want to support are listed at the bottom.

Limitations

In NativeScript we don’t need to implement all details of the web workers specification, because some of these details are only related to the browser, and won’t have any meaning in the context of a NativeScript application. The features and syntax that will follow has the purpose of describing the adoption of the web workers specification in NativeScript. In this document we will describe and list what we intend to support.

Guidelines from the specification to follow

context

Notice that onmessage and postMessage() need to be hung off the Worker object when used in the main script thread, but not when used in the worker. This is because, inside the worker, the worker is effectively the global scope.

messaging specifics

Data passed between the main thread and workers is copied, not shared. Objects are serialized as they're handed to the worker, and subsequently, deserialized on the other end. The page and worker do not share the same instance, so the end result is that a duplicate is created on each end. Most browsers implement this feature as structured cloning.
The structured cloning algorithm can accept JSON and a few things that JSON can't — like circular references.

messaging transfer ownership

Passing data by transferring ownership (transferable objects): in nativescript we can try to pass native objects by transferring ownership on them.

onerror spec

The error event has the following three fields that are of interest:
message: A human-readable error message.
filename: The name of the script file in which the error occurred.
lineno: The line number of the script file on which the error occurred.

importScripts

ImportScripts in a nativescript context mean require. If we want to support this syntax we need an alias for require.

Syntax

The following example will be the general syntax to use when working with Workers. Syntax is based on web worker specification.

<app_name>/app/main.js

// create new worker
var myWorker = new Worker("worker.js");

// on worker error handler
myWorker.onerror = function (e) {
 //do on worker error
}

// receive messages from worker
myWorker.onmessage = function (e) {
  result.textContent = e.data;
}
....
// send messages to worker
myWorker.postMessage("message will be sent to worker");

//The worker thread is killed immediately without an opportunity to complete its operations or clean up after itself.
myWorker.terminate();

myWorker.onclose = function () {
//on close (clean up)
}

<app_name>/app/worker.js

onmessage = function(e) {
  postMessage(e.data); // worker -> main
}

//worker closes it self
close();

Supported APIs:

Worker Object:

  • Worker.onerror
  • Worker.onmessage
  • Worker.postMessage()
  • Worker.terminate()

Worker Global Object:

  • WorkerGlobalObject.self
  • WorkerGlobalObject.onmessage
  • WorkerGlobalObject.onerror
  • WorkerGlobalObject.onclose
  • WorkerGlobalObject.close()
  • WorkerGlobalObject.postMessage()
  • WorkerGlobalObject.importScripts()

Implementation steps:

  • Run pure javascript file on new thread
    • Initialize the android runtime on new thread
    • Create a run loop on the newly created thread and schedule the runtime on it
  • Enable require of pure javascript files from new thread
  • Implement error handling per thread
    • Try/catch should work like it does in the main thread
    • When error is not caught, call onerror handler of the worker object on the main thread
    • Throw error when calling UI APIs on worker thread (if possible)
  • Call native non-ui APIs from javascript file running on thread
    • android runtime tests pass on worker thread
  • Implement messaging API to communicate between threads (main -> worker, worker->main)
    • Simple object
    • Simple object and an ArrayBuffer as a second parameter
    • Native objects (change of ownership instead of copy)
  • Enable tns-module support (will extend later)
  • Debugging
    • Resarch what should be done on the backend side in order to support worker debugging
    • Debugging support in NativeScriptAppInspector client
    • Debugging support in VSCode
  • Add tests
    • Callback tests
    • Parameters/Properties/Ctor tests
    • Lifecycle tests
    • GC tests
  • Update documentation
@NathanaelA

This comment was marked as abuse.

@blagoev
Copy link
Contributor

blagoev commented Aug 12, 2016

Also consider researching support for shared array buffers and atomics
as per the spec https://github.com/tc39/ecmascript_sharedmem

This is already under a flag in Chrome 48+

@NathanaelA

This comment was marked as abuse.

@petekanev
Copy link
Contributor

@NathanaelA while experimenting with the worker API, I too wished that I could pass just a string, but could not. In the end it all boils down to a string, that is compiled in v8; only one has a source, and the other - doesn't, so it should be possible. Can't say yet if it will be in on the end of the first iteration of the WebWorkers support.

@atanasovg atanasovg added this to the 2.3.0 milestone Aug 25, 2016
@roblav96
Copy link

@NathanaelA How does one go about experimenting with the preview channel of the WebWorkers?

@petekanev
Copy link
Contributor

@roblav96 when they are ready, they will be available in the master branch for others to test and experiment with.
You can normally fetch the latest unstable changes from the master by invoking tns platform add android@next

@roblav96
Copy link

@Pip3r4o So are you saying I can run tns platform add android@next to experiment with the WebWorkers? :D

@petekanev
Copy link
Contributor

@roblav96 as soon as they make it into the master branch, yes. We will let you know when that is.

@NathanaelA

This comment was marked as abuse.

@roblav96
Copy link

@Pip3r4o Awesome! Thanks so much!

How might one go about applying for dev branch access? I would really like to become more apart of the community. Hopefully going to nativescript developer day will help with that.

@petekanev
Copy link
Contributor

@roblav96 you already have access to 99.9% of what we develop, the Web Workers are still going through internal discussions, architectural planning, and more. We will release a statement very soon with more details on what we have been working on this past month.

We are always glad to welcome new contributors. You can begin by becoming familiar with the execution flow of an android application, and the more "advanced" topics in our runtimes docs.
There is also an ongoing effort to better document our code, so for now you may be able to understand just enough when going through the specific implementations in our runtimes.

@roblav96
Copy link

@Pip3r4o Thanks for the heads up. I started my NativeScript endeavour about a month ago and am completely obsessed. I don't consider myself a ninja yet, but I do agree I've come across some things that should be added to the docs. Most importantly how to use android-dts-generator. Am I allowed to to submit a PR to the docs repository? I would like to, but I won't if it will go to waste.

@petekanev
Copy link
Contributor

petekanev commented Aug 30, 2016

@roblav96 Absolutely, please do! The submodule android-runtime-docs may be more appropriate for the dts-generator. Also, hit me up in our Community Slack (peter.kanev), and I'll assist you with what I can in my spare time!

@roblav96
Copy link

@Pip3r4o Awesome! Sounds great. I look forward to expanding the documentation. Thank you!

@roblav96
Copy link

Quick question, would we be able to implement a CursorLoader with this as suggested on the official android docs?

@petekanev
Copy link
Contributor

Workers are slowly crawling their way out, I've updated the checklist above to reflect on what has been implemented so far in the android runtime (multithreading branch). Testing is underway, but unit tests alone will not suffice. Do you have any mini scenarios (please be very specific - plugins, work volume?) that you would like to see implemented? One of the tests performs heavy-duty work on a Worker thread, but we need something not so... generic? @NathanaelA @sitefinitysteve

@NathanaelA

This comment was marked as abuse.

@petekanev
Copy link
Contributor

@NathanaelA The current implementation of the Workers will be available at @next as soon as we merge the latest v8 into master, which will be any day now, making sure we got everything right, since updating the v8 imposed numerous breaking changes we had to take care of to ensure correct runtime behavior.

@NathanaelA

This comment was marked as abuse.

@roblav96
Copy link

@Pip3r4o
I would certainly like to see retrieving contacts through a CursorLoader implemented and android.
On iOS I think a good test would be to unzip a large archive using ZipArchive.

@Plamen5kov Plamen5kov removed this from the 2.4.0 milestone Nov 16, 2016
@Plamen5kov
Copy link
Contributor Author

Plamen5kov commented Nov 16, 2016

Work on this issue/story will be resumed as soon as we gather enough feedback for the current state of the Workers

@dxshindeo
Copy link

In your article (http://developer.telerik.com/featured/benefits-single-threading-model-nativescript/), you mentioned:

For a significant number of these scenarios, NativeScript modules and plugins already exist to do the generic work on a background thread, dispatching the output to the main UI thread when the heavy lifting is done. The out-of-the-box HTTP module, for example, uses a background thread to process network requests

Does the same applies to the fetch module?

@slavchev
Copy link

slavchev commented Jan 4, 2017

@dxshindeo Yes, the current fetch module implementation uses xhr module which in turn uses http module.

@dxshindeo
Copy link

Thanks for clearing that up :)

@dxshindeo
Copy link

WEB WORKER IS NOT WORKING IN API17-API19!!! Didn't test API20, but I reccon it also won't work.
Web Worker successfully works in API21-API25.
This is a major dealbreaker!

@petekanev
Copy link
Contributor

@dxshindeo it would be of great help if you shared what happens on devices of lower API level and what isn't working for you. Thank you!

@dxshindeo
Copy link

dxshindeo commented Jan 4, 2017

Basically, main file:

var sockets_worker = new Worker('../../workers/sockets');

console.log("A");
sockets_worker.postMessage({
	doesnt_matter_test_var: "testA"
});

sockets_worker.onmessage = function(msg) {
	console.log("C");
}

And worker file:

require('globals'); // necessary to bootstrap tns modules on the new thread

onmessage = function(msg) {
	console.log("B");

	postMessage({
		doesnt_matter_test_var: "testB"
	});
}

This works fine in API21+, but in lesser APIs I get only console.log("A"); , no other console.log is given out.

@petekanev
Copy link
Contributor

petekanev commented Jan 4, 2017

@dxshindeo I just tested the above, and it works out well for me, messages are logged properly and communication works back and forth. Tested on Geny Emulator API 17 and native AVD API18 Emulator. Please provide more details like crash logs, or steps to reproduce the behavior you are experiencing. Thanks!

@dxshindeo
Copy link

Hmm now with the above example, sample project tns create test --ng, even APIs 21+ wont call it.
Narrowed down the problem - it seems like the postMessage from main file does not run, because the worker itself hasn't had the time to initialize, which means we would need some sort of callback.

Current workaround for me is to wrap that postMessage in setTimeout.

// app.component.ts

import { Component } from "@angular/core";

var sockets_worker = new Worker('./worker');

@Component({
    selector: "my-app",
    templateUrl: "app.component.html",
})
export class AppComponent {
    public counter: number = 16;

    public get message(): string {
        if (this.counter > 0) {
            return this.counter + " taps left";
        } else {
            return "Hoorraaay! \nYou are ready to start building!";
        }
    }
    
    public onTap() {
        this.counter--;
        sockets_worker.postMessage({
            doesnt_matter_test_var: "testA"
        });
    }

    ngAfterViewInit() {
        console.log("A");

        sockets_worker.onmessage = function(msg) {
            console.log("C");
        }

        setTimeout(function(){
            sockets_worker.postMessage({
                doesnt_matter_test_var: "testA"
            });
        }, 1);

    }
}

@petekanev
Copy link
Contributor

@dxshindeo the implementation of the workers is such that postMessage will execute, and the message on the other end will be received as soon as its thread has finished initializing, so naturally a setTimeout should not be necessary

@dxshindeo
Copy link

Since this issue is about threads, I guess the following question fits here as well - when using tns-core-modules/connectivity, can we leave its code in "main file" (main thread) or do we have to put it in worker (new thread), for max performance?

@petekanev
Copy link
Contributor

@dxshindeo I think leaving the calls to the main thread is fairly safe and should not be offloaded to a worker thread. In fact a new thread will likely have a negative impact to the app's available memory if it only deals with the connectivity functionality.

@dxshindeo
Copy link

Big thanks for clearing that up!

@sktzoootech
Copy link

I am using tns 2.5 and postMessage from main thread works fine but the one on the worker thread doesn't work. When on the worker I use the following syntax to post message 'postMessage({src: "test"}, "tempo")'. The extra parameter is a targetOrigin of type string but I got no idea what that is. So the error showing up is "The second parameter of postMessage must be array, null or undefined. If I substitute it with null it still gives me the same error.

Not sure what I am doing wrong, can someone help me please. :)

@sktzoootech
Copy link

Ok never mind it seems like the null worked I must have forgot to save it before I run build.

@petekanev petekanev changed the title [Proposal]: Support for Workers Workers API Mar 12, 2018
@mohammadrafigh
Copy link

is there any way to use transferable list as current state? postMessage accepts just 1 argument and throws exception when I provide a transferable list.

@dxshindeo
Copy link

last I checked you can only transfer primitives

@petekanev
Copy link
Contributor

You can transfer objects recursively, but they can only contain primitives.

@farfromrefug
Copy link
Contributor

Is there any plan to move forward on this? I would love to be able to pass native object.
Also i asked on the module repo but got no answer. Is there a way to combine this with a service? Which means having an Android background service running on its own thread? (bluetooth, udp, tcp ...)

@darind
Copy link
Collaborator

darind commented Dec 18, 2018

@farfromrefug, currently there are some technical limitations which prevent from passing native objects back and forth background workers.

As a workaround you could store your native objects into some java dictionary and only pass the key to the worker thread.

For example you could define a centralized dictionary:

package com.myapp;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public final class WorkersContext {
    private final static Map<String, Object> container = new ConcurrentHashMap<String, Object>();

    public static Object getValue(String key) {
        return container.get(key);
    }

    public static void setValue(String key, Object value) {
        container.put(key, value);
    }
}

which you could use to persist your native object:

var worker = new Worker("./EvalWorker.js");
var nativeObject = new java.io.File("aaa.js");
com.myapp.WorkersContext.setValue("my_key", nativeObject);
var message = {
    value: { key: "my_key" }
};
worker.postMessage(message);

and then in your background worker retrieve the stored native object:

onmessage = function(msg) {
    var key = msg.data.value.key;
    var nativeObject = com.myapp.WorkersContext.getValue(key);
    // ... use the native object here
}

@farfromrefug
Copy link
Contributor

@darind that's actually a nice idea and easy enough to implement. Would need to find a counterpart for iOS though.
Do you have any idea about the service thing? Right now i do have a service extending android.app.Service And in this i run my bluetooth code. But the fact that any js is run on the main thread makes me really wonder about the real use of my service right now.

@jerbob92
Copy link

Does anyone else have issues using existing code NativeScript/Angular code in the worker?
I always get "Error: The AngularCompilerPlugin was not found. The @ngtools/webpack loader requires the plugin.", and I can't seem to get around it.

@sferrando89
Copy link

Hello, I'm having troubles using the geolocation plugin (nativescript-geolocation) inside a worker. I got the following error:

Error: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Context.getPackageName()' on a null object reference

The error shows when i use the required plugin and execute one of it's functions:

const geolocation = require("nativescript-geolocation");
const { Accuracy } = require("tns-core-modules/ui/enums");
...
async function getLocation() {

await geolocation
    .getCurrentLocation({                           <= here explodes.
        desiredAccuracy: Accuracy.high,
        maximumAge: 5000,
        timeout: 20000
    })
    .then(res => {

...

Is there a way around? or am I doing something wrong?

Thx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests