All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gwt.core.client.impl.AsyncFragmentLoader Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2008 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.gwt.core.client.impl;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.RunAsyncCallback;

/**
 * 

* Low-level support to download an extra fragment of code. This should not be * invoked directly by user code. *

* *

* The fragments are numbered as follows, assuming there are m split * points: * *

    *
  • 0 -- the base fragment, which is initially downloaded *
  • 1..m -- fragments for each split point *
  • m+1 -- the leftovers fragment of code that goes nowhere else *
* *

* Since the precise way to load code depends on the linker, linkers should * specify a rebind of {@link LoadingStrategy}. */ public class AsyncFragmentLoader { /** * A strategy for loading code fragments. */ public interface LoadingStrategy { void startLoadingFragment(int fragment, LoadTerminatedHandler loadTerminatedHandler); } /** * An interface for handlers of load completion. On a failed download, this * callback should be invoked or else the requested download will hang * indefinitely. On a successful download, it's optional to call this method. * If it is called at all, it must be called after the downloaded code has * been installed, so that {@link AsyncFragmentLoader} can distinguish * successful from unsuccessful downloads. */ public interface LoadTerminatedHandler { void loadTerminated(Throwable reason); } /** * A strategy for logging progress. */ public interface Logger { /** * Log an event. The fragment and size are boxed * so that they can be optional. A value of null for either one * means that they are not specified. */ void logEventProgress(String eventGroup, String type, int fragment, int size); } /** * Labels used for runAsync lightweight metrics. */ public static class LwmLabels { public static final String BEGIN = "begin"; public static final String END = "end"; private static final String LEFTOVERS_DOWNLOAD = "leftoversDownload"; private static String downloadGroupForExclusive(int splitPoint) { return "download" + splitPoint; } } /** * The standard logger used in a web browser. It uses the lightweight metrics * system. */ public static class StandardLogger implements Logger { /** * Always use this as {@link #isStatsAvailable()} && * {@link #stats(JavaScriptObject)}. */ private static native boolean stats(JavaScriptObject data) /*-{ return $stats(data); }-*/; @Override public void logEventProgress(String eventGroup, String type, int fragment, int size) { @SuppressWarnings("unused") boolean toss = isStatsAvailable() && stats(createStatsEvent(eventGroup, type, fragment, size)); } private native JavaScriptObject createStatsEvent(String eventGroup, String type, int fragment, int size) /*-{ var evt = { moduleName: @com.google.gwt.core.client.GWT::getModuleName()(), sessionId: $sessionId, subSystem: 'runAsync', evtGroup: eventGroup, millis: (new Date()).getTime(), type: type }; if (fragment >= 0) { evt.fragment = fragment; } if (size >= 0) { evt.size = size; } return evt; }-*/; private native boolean isStatsAvailable() /*-{ return !!$stats; }-*/; } /** * An exception indicating than at HTTP download failed. */ static class HttpDownloadFailure extends RuntimeException { private final int statusCode; public HttpDownloadFailure(String url, int statusCode, String statusText) { super("Download of " + url + " failed with status " + statusCode + "(" + statusText + ")"); this.statusCode = statusCode; } public int getStatusCode() { return statusCode; } } /** * An exception indicating than at HTTP download succeeded, but installing its * body failed. */ static class HttpInstallFailure extends RuntimeException { public HttpInstallFailure(String url, String text, Throwable rootCause) { super("Install of " + url + " failed with text " + text, rootCause); } } /** * A trivial queue of int's that should compile much better than a * LinkedList<Integer>. It assumes that it has a bound on the number of * items added to the queue. Removing items does not free up more space, but * calling clear() does. */ private static class BoundedIntQueue { private final int[] array; private int read = 0; private int write = 0; public BoundedIntQueue(int maxPuts) { array = new int[maxPuts]; } public void add(int x) { assert (write < array.length); array[write++] = x; } /** * Removes all elements, and also makes all space in the queue available * again. */ public void clear() { read = 0; write = 0; } public int peek() { assert read < write; return array[read]; } public int remove() { assert read < write; return array[read++]; } public int size() { return write - read; } } /** * Internal load error handler. This calls all user-provided error handlers * and cancels all pending downloads. */ private class ResetAfterDownloadFailure implements LoadTerminatedHandler { private final int fragment; public ResetAfterDownloadFailure(int myFragment) { this.fragment = myFragment; } @Override public void loadTerminated(Throwable reason) { if (fragmentLoading != fragment) { // fragment already loaded successfully return; } // Cancel all pending downloads. /* * Make a local list of the handlers to run, in case one of them calls * another runAsync */ LoadTerminatedHandler[] handlersToRun = pendingDownloadErrorHandlers; pendingDownloadErrorHandlers = new LoadTerminatedHandler[numEntries + 1]; /* * Call clear() here so that requestedExclusives makes all of its space * available for later requests. */ requestedExclusives.clear(); fragmentLoading = -1; /* * Run the handlers. If an exception is thrown while canceling any of * them, remember and throw the last one. */ RuntimeException lastException = null; for (LoadTerminatedHandler handler : handlersToRun) { if (handler != null) { try { handler.loadTerminated(reason); } catch (RuntimeException e) { lastException = e; } } } if (lastException != null) { throw lastException; } } } /** * The standard instance of AsyncFragmentLoader used in a web browser. Outside * of GWT generated JavaScript (i.e our vanilla JUnit tests, or if referenced * in a server context), this field is {@code null}. When compiled to * JavaScript, the parameters to this call are rewritten by * {@link com.google.gwt.dev.jjs.impl.codesplitter.ReplaceRunAsyncs}. So this must be a * method call of exactly two arguments to succeed when invoked in web mode. */ public static AsyncFragmentLoader BROWSER_LOADER = makeBrowserLoader(1, new int[]{}); /** * Called by compiler-generated code when a fragment is loaded. * * @param fragment the fragment number */ public static void onLoad(int fragment) { BROWSER_LOADER.onLoadImpl(fragment); } /** * Called by the compiler to implement {@link GWT#runAsync}. * * @param fragment the fragment number * @param callback the callback to run */ public static void runAsync(int fragment, RunAsyncCallback callback) { BROWSER_LOADER.runAsyncImpl(fragment, callback); } /** * Creates the loader stored as {@link #BROWSER_LOADER}. * * @returns {@code null} if not in GWT client code, where * {@link GWT#create(Class)} cannot be used, or a fragment loader for * the user's application otherwise. */ private static AsyncFragmentLoader makeBrowserLoader(int numFragments, int initialLoad[]) { if (GWT.isClient()) { return new AsyncFragmentLoader(numFragments, initialLoad, (LoadingStrategy) GWT.create(LoadingStrategy.class), (Logger) GWT.create(Logger.class), (OnSuccessExecutor) GWT.create(OnSuccessExecutor.class)); } else { return null; } } private final OnSuccessExecutor onSuccessExecutor; /** * Callbacks indexed by fragment number. */ private final Object[][] allCallbacks; /** * The fragment currently loading, or -1 if there aren't any. */ private int fragmentLoading = -1; /** * The sequence of fragments to load initially, before anything else can be * loaded. This array will hold the initial sequence of bases followed by the * leftovers fragment. It is filled in by * {@link com.google.gwt.dev.jjs.impl.codesplitter.CodeSplitter} modifying the initializer * to {@link #BROWSER_LOADER}. The list does not include the * leftovers fragment, which must be loaded once all of these are finished. */ private final int[] initialLoadSequence; /** * This array indicates which fragments have been successfully loaded. */ private final boolean[] isLoaded; private final LoadingStrategy loadingStrategy; private final Logger logger; /** * The total number of entry points in the program, which is the number of * split points plus one for the main entry point of the program. */ private final int numEntries; /** * Externally provided handlers for all outstanding and queued download * requests. */ private LoadTerminatedHandler[] pendingDownloadErrorHandlers; /** * Whether prefetching is currently enabled. */ private boolean prefetching = false; /** * This queue has fragments that have been requested to be prefetched. If it's * null, that indicates no prefetch requests, which should cause * all of this class's prefetching code to drop out of the compiled output. */ private BoundedIntQueue prefetchQueue = null; /** * Base fragments that remain to be downloaded. It is lazily initialized in * the first call to {@link #startLoadingNextFragment()}. It does include the * leftovers fragment. */ private BoundedIntQueue remainingInitialFragments = null; /** * Exclusive fragments that have been requested but that are not yet * downloading. */ private final BoundedIntQueue requestedExclusives; public AsyncFragmentLoader(int numEntries, int[] initialLoadSequence, LoadingStrategy loadingStrategy, Logger logger, OnSuccessExecutor executor) { this.numEntries = numEntries; this.initialLoadSequence = initialLoadSequence; this.loadingStrategy = loadingStrategy; this.logger = logger; this.onSuccessExecutor = executor; int numEntriesPlusOne = numEntries + 1; this.allCallbacks = new Object[numEntriesPlusOne][]; this.requestedExclusives = new BoundedIntQueue(numEntriesPlusOne); this.isLoaded = new boolean[numEntriesPlusOne]; this.pendingDownloadErrorHandlers = new LoadTerminatedHandler[numEntriesPlusOne]; } public boolean isAlreadyLoaded(int splitPoint) { return isLoaded[splitPoint]; } /** * Request that a sequence of split points be prefetched. Code for the split * points in splitPoints will be downloaded and installed * whenever there is nothing else to download. Each call to this method * overwrites the entire prefetch queue with the newly specified one. */ public void setPrefetchQueue(int... runAsyncSplitPoints) { if (prefetchQueue == null) { prefetchQueue = new BoundedIntQueue(numEntries); } prefetchQueue.clear(); for (int sp : runAsyncSplitPoints) { prefetchQueue.add(sp); } startLoadingNextFragment(); } public void startPrefetching() { prefetching = true; startLoadingNextFragment(); } public void stopPrefetching() { prefetching = false; } /** * Inform the loader that a fragment has now finished loading. */ void fragmentHasLoaded(int fragment) { logFragmentLoaded(fragment); if (fragment < pendingDownloadErrorHandlers.length) { pendingDownloadErrorHandlers[fragment] = null; } if (isInitial(fragment)) { assert (fragment == remainingInitialFragments.peek()); remainingInitialFragments.remove(); } assert (fragment == fragmentLoading); fragmentLoading = -1; assert !isLoaded[fragment]; isLoaded[fragment] = true; startLoadingNextFragment(); } /** * Requests a load of the code for the specified split point. If the load * fails, loadErrorHandler will be invoked. If it succeeds, then * the code will be installed, and the code is expected to invoke its own * on-success hooks, including a call to either * {@link #leftoversFragmentHasLoaded()} or {@link #fragmentHasLoaded(int)}. * * @param splitPoint the split point whose code needs to be loaded */ void inject(int splitPoint, LoadTerminatedHandler loadErrorHandler) { pendingDownloadErrorHandlers[splitPoint] = loadErrorHandler; if (!isInitial(splitPoint)) { requestedExclusives.add(splitPoint); } startLoadingNextFragment(); } void leftoversFragmentHasLoaded() { onLoadImpl(leftoversFragment()); } private boolean anyPrefetchesRequested() { return prefetching && prefetchQueue != null && prefetchQueue.size() > 0; } /** * Clear out any inject and prefetch requests that are already loaded. Only * remove items from the head of each queue; any stale entries later in the * queue will be removed later. */ private void clearRequestsAlreadyLoaded() { while (requestedExclusives.size() > 0 && isLoaded[requestedExclusives.peek()]) { int offset = requestedExclusives.remove(); if (offset < pendingDownloadErrorHandlers.length) { pendingDownloadErrorHandlers[offset] = null; } } if (prefetchQueue != null) { while (prefetchQueue.size() > 0 && isLoaded[prefetchQueue.peek()]) { prefetchQueue.remove(); } } } private String downloadGroup(int fragment) { return (fragment == leftoversFragment()) ? LwmLabels.LEFTOVERS_DOWNLOAD : LwmLabels .downloadGroupForExclusive(fragment); } /** * Return whether all initial fragments have completed loading. */ private boolean haveInitialFragmentsLoaded() { return remainingInitialFragments != null && remainingInitialFragments.size() == 0; } /** * Initialize {@link #remainingInitialFragments} if it isn't already. */ private void initializeRemainingInitialFragments() { if (remainingInitialFragments == null) { remainingInitialFragments = new BoundedIntQueue(initialLoadSequence.length + 1); for (int sp : initialLoadSequence) { remainingInitialFragments.add(sp); } remainingInitialFragments.add(leftoversFragment()); } } /** * Returns true if array contains only null * elements. */ private boolean isEmpty(Object[] array) { for (int i = 0; i < array.length; i++) { if (array[i] != null) { return false; } } return true; } private boolean isInitial(int splitPoint) { if (splitPoint == leftoversFragment()) { return true; } for (int sp : initialLoadSequence) { if (sp == splitPoint) { return true; } } return false; } private boolean isLoading(int splitPoint) { return pendingDownloadErrorHandlers[splitPoint] != null; } private int leftoversFragment() { return numEntries; } private void logDownloadStart(int fragment) { logEventProgress(downloadGroup(fragment), LwmLabels.BEGIN, fragment, -1); } /** * Log an event with the {@Logger} this instance was provided. */ private void logEventProgress(String eventGroup, String type) { logEventProgress(eventGroup, type, -1, -1); } /** * Log event progress via the {@link Logger} this instance was provided. The * fragment and size objects are allowed to be * null. */ private void logEventProgress(String eventGroup, String type, int fragment, int size) { logger.logEventProgress(eventGroup, type, fragment, size); } private void logFragmentLoaded(int fragment) { String logGroup = downloadGroup(fragment); logEventProgress(logGroup, LwmLabels.END, fragment, -1); } private void onLoadImpl(int fragment) { fragmentHasLoaded(fragment); Object[] callbacks = allCallbacks[fragment]; if (callbacks != null) { logEventProgress("runCallbacks" + fragment, "begin"); allCallbacks[fragment] = null; for (Object callback : callbacks) { try { ((RunAsyncCallback) callback).onSuccess(); } catch (Throwable t) { GWT.reportUncaughtException(t); } } logEventProgress("runCallbacks" + fragment, "end"); } } private void runAsyncImpl(final int fragment, RunAsyncCallback callback) { if (isLoaded[fragment]) { assert allCallbacks[fragment] == null; this.onSuccessExecutor.execute(this, callback); return; } Object[] callbacks = allCallbacks[fragment]; if (callbacks == null) { callbacks = allCallbacks[fragment] = new RunAsyncCallback[0]; } // Take advantage of no range checking in web mode. assert GWT.isScript(); callbacks[callbacks.length] = callback; if (!isLoading(fragment)) { inject(fragment, new AsyncFragmentLoader.LoadTerminatedHandler() { @Override public void loadTerminated(Throwable reason) { Object[] callbacks = allCallbacks[fragment]; if (callbacks != null) { allCallbacks[fragment] = null; for (Object callback : callbacks) { ((RunAsyncCallback) callback).onFailure(reason); } } } }); } } void executeOnSuccess0(RunAsyncCallback callback) { /* * Calls on {@link RunAsyncCallback#onSuccess} from {@link AsyncFragmentLoader} is special * treated (See RescueVisitor in ControlFlowAnalyzer) so that code splitter will not follow them * on fragment analysis. That is, if don't call onSuccess from here and instead call it directly * from the scheduled command, then it will make the code splitter put the split point code in * the initial fragment. */ callback.onSuccess(); } private void startLoadingFragment(int fragment) { assert (fragmentLoading < 0); fragmentLoading = fragment; logDownloadStart(fragment); loadingStrategy.startLoadingFragment(fragment, new ResetAfterDownloadFailure(fragment)); } /** * Start downloading the next fragment queued up, if there are any. */ private void startLoadingNextFragment() { if (fragmentLoading >= 0) { // Already loading something return; } initializeRemainingInitialFragments(); clearRequestsAlreadyLoaded(); if (isEmpty(pendingDownloadErrorHandlers) && !anyPrefetchesRequested()) { /* * Don't load anything if there aren't any requests outstanding. */ return; } // Check if an initial needs downloading if (remainingInitialFragments.size() > 0) { startLoadingFragment(remainingInitialFragments.peek()); return; } assert (haveInitialFragmentsLoaded()); // Check if an exclusive is pending if (requestedExclusives.size() > 0) { startLoadingFragment(requestedExclusives.remove()); return; } // Check the prefetch queue if (anyPrefetchesRequested()) { startLoadingFragment(prefetchQueue.remove()); return; } // Nothing needed downloading after all?! assert false; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy