changeset 5233:e1f9a085f399

RT-33262: Not able to use Task and Service from the init method of Application Reviewed-by: kcr
author rbair
date Wed, 02 Oct 2013 13:56:01 -0700
parents 043bd9ac4de2
children d70c498de565
files apps/toys/Hello/src/main/java/hello/HelloTask.java modules/graphics/src/main/java/javafx/concurrent/Service.java modules/graphics/src/main/java/javafx/concurrent/Task.java modules/graphics/src/main/java/javafx/geometry/Bounds.java modules/graphics/src/test/java/javafx/concurrent/AbstractTask.java modules/graphics/src/test/java/javafx/concurrent/ServiceLifecycleTest.java modules/graphics/src/test/java/javafx/concurrent/ServiceTest.java modules/graphics/src/test/java/javafx/concurrent/TestServiceFactory.java
diffstat 8 files changed, 891 insertions(+), 52 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/apps/toys/Hello/src/main/java/hello/HelloTask.java	Wed Oct 02 13:56:01 2013 -0700
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package hello;
+
+import javafx.application.Application;
+import javafx.concurrent.Task;
+import javafx.geometry.Pos;
+import javafx.scene.Scene;
+import javafx.scene.control.Label;
+import javafx.scene.control.ProgressBar;
+import javafx.scene.layout.VBox;
+import javafx.stage.Stage;
+
+/**
+ */
+public class HelloTask extends Application {
+    private ProgressBar bar = new ProgressBar();
+    private Label label = new Label();
+
+    @Override
+    public void init() throws Exception {
+        Task<String> task = new Task<String>() {
+            @Override protected String call() throws Exception {
+                for (int i=0; i<100; i++) {
+                    Thread.sleep(100);
+                    updateProgress(i, 99);
+                }
+                return "Finished!";
+            }
+        };
+        bar.progressProperty().bind(task.progressProperty());
+        label.textProperty().bind(task.valueProperty());
+        Thread th = new Thread(task);
+        th.setDaemon(false);
+        th.start();
+    }
+
+    @Override
+    public void start(Stage stage) throws Exception {
+        VBox root = new VBox(15, bar, label);
+        root.setAlignment(Pos.CENTER);
+        Scene s = new Scene(root, 640, 480);
+        stage.setScene(s);
+        stage.show();
+    }
+
+    public static void main(String[] args) {
+        launch(args);
+    }
+}
--- a/modules/graphics/src/main/java/javafx/concurrent/Service.java	Wed Oct 02 13:40:16 2013 -0700
+++ b/modules/graphics/src/main/java/javafx/concurrent/Service.java	Wed Oct 02 13:56:01 2013 -0700
@@ -69,7 +69,11 @@
  *     and is designed to relieve the application developer from the burden
  *     of manging multithreaded code that interacts with the user interface. As
  *     such, all of the methods and state on the Service are intended to be
- *     invoked exclusively from the JavaFX Application thread.
+ *     invoked exclusively from the JavaFX Application thread. The only exception
+ *     to this, is when initially configuring a Service, which may safely be done
+ *     from any thread, and initially starting a Service, which may also safely
+ *     be done from any thread. However, once the Service has been initialized and
+ *     started, it may only thereafter be used from the FX thread.
  * </p>
  * <p>
  *     Service implements {@link Worker}. As such, you can observe the state of
@@ -197,15 +201,15 @@
         EXECUTOR.allowCoreThreadTimeOut(true);
     }
 
-    private final ObjectProperty<State> state = new SimpleObjectProperty<State>(this, "state", State.READY);
+    private final ObjectProperty<State> state = new SimpleObjectProperty<>(this, "state", State.READY);
     @Override public final State getState() { checkThread(); return state.get(); }
     @Override public final ReadOnlyObjectProperty<State> stateProperty() { checkThread(); return state; }
 
-    private final ObjectProperty<V> value = new SimpleObjectProperty<V>(this, "value");
+    private final ObjectProperty<V> value = new SimpleObjectProperty<>(this, "value");
     @Override public final V getValue() { checkThread(); return value.get(); }
     @Override public final ReadOnlyObjectProperty<V> valueProperty() { checkThread(); return value; }
 
-    private final ObjectProperty<Throwable> exception = new SimpleObjectProperty<Throwable>(this, "exception");
+    private final ObjectProperty<Throwable> exception = new SimpleObjectProperty<>(this, "exception");
     @Override public final Throwable getException() { checkThread(); return exception.get(); }
     @Override public final ReadOnlyObjectProperty<Throwable> exceptionProperty() { checkThread(); return exception; }
 
@@ -226,22 +230,22 @@
     @Override public final ReadOnlyBooleanProperty runningProperty() { checkThread(); return running; }
 
     private final StringProperty message = new SimpleStringProperty(this, "message", "");
-    @Override public final String getMessage() { return message.get(); }
-    @Override public final ReadOnlyStringProperty messageProperty() { return message; }
+    @Override public final String getMessage() { checkThread(); return message.get(); }
+    @Override public final ReadOnlyStringProperty messageProperty() { checkThread(); return message; }
 
     private final StringProperty title = new SimpleStringProperty(this, "title", "");
-    @Override public final String getTitle() { return title.get(); }
-    @Override public final ReadOnlyStringProperty titleProperty() { return title; }
+    @Override public final String getTitle() { checkThread(); return title.get(); }
+    @Override public final ReadOnlyStringProperty titleProperty() { checkThread(); return title; }
 
     /**
      * The executor to use for running this Service. If no executor is specified, then
      * a new daemon thread will be created and used for running the Service using some
      * default executor.
      */
-    private final ObjectProperty<Executor> executor = new SimpleObjectProperty<Executor>(this, "executor");
+    private final ObjectProperty<Executor> executor = new SimpleObjectProperty<>(this, "executor");
     public final void setExecutor(Executor value) { checkThread(); executor.set(value); }
     public final Executor getExecutor() { checkThread(); return executor.get(); }
-    public final ObjectProperty<Executor> executorProperty() { return executor; }
+    public final ObjectProperty<Executor> executorProperty() { checkThread(); return executor; }
 
     /**
      * The onReady event handler is called whenever the Task state transitions
@@ -251,6 +255,7 @@
      * @since JavaFX 2.1
      */
     public final ObjectProperty<EventHandler<WorkerStateEvent>> onReadyProperty() {
+        checkThread();
         return getEventHelper().onReadyProperty();
     }
 
@@ -262,6 +267,7 @@
      * @since JavaFX 2.1
      */
     public final EventHandler<WorkerStateEvent> getOnReady() {
+        checkThread();
         return eventHelper == null ? null : eventHelper.getOnReady();
     }
 
@@ -273,6 +279,7 @@
      * @since JavaFX 2.1
      */
     public final void setOnReady(EventHandler<WorkerStateEvent> value) {
+        checkThread();
         getEventHelper().setOnReady(value);
     }
 
@@ -293,6 +300,7 @@
      * @since JavaFX 2.1
      */
     public final ObjectProperty<EventHandler<WorkerStateEvent>> onScheduledProperty() {
+        checkThread();
         return getEventHelper().onScheduledProperty();
     }
 
@@ -304,6 +312,7 @@
      * @since JavaFX 2.1
      */
     public final EventHandler<WorkerStateEvent> getOnScheduled() {
+        checkThread();
         return eventHelper == null ? null : eventHelper.getOnScheduled();
     }
 
@@ -315,6 +324,7 @@
      * @since JavaFX 2.1
      */
     public final void setOnScheduled(EventHandler<WorkerStateEvent> value) {
+        checkThread();
         getEventHelper().setOnScheduled(value);
     }
 
@@ -335,6 +345,7 @@
      * @since JavaFX 2.1
      */
     public final ObjectProperty<EventHandler<WorkerStateEvent>> onRunningProperty() {
+        checkThread();
         return getEventHelper().onRunningProperty();
     }
 
@@ -346,6 +357,7 @@
      * @since JavaFX 2.1
      */
     public final EventHandler<WorkerStateEvent> getOnRunning() {
+        checkThread();
         return eventHelper == null ? null : eventHelper.getOnRunning();
     }
 
@@ -357,6 +369,7 @@
      * @since JavaFX 2.1
      */
     public final void setOnRunning(EventHandler<WorkerStateEvent> value) {
+        checkThread();
         getEventHelper().setOnRunning(value);
     }
 
@@ -377,6 +390,7 @@
      * @since JavaFX 2.1
      */
     public final ObjectProperty<EventHandler<WorkerStateEvent>> onSucceededProperty() {
+        checkThread();
         return getEventHelper().onSucceededProperty();
     }
 
@@ -388,6 +402,7 @@
      * @since JavaFX 2.1
      */
     public final EventHandler<WorkerStateEvent> getOnSucceeded() {
+        checkThread();
         return eventHelper == null ? null : eventHelper.getOnSucceeded();
     }
 
@@ -399,6 +414,7 @@
      * @since JavaFX 2.1
      */
     public final void setOnSucceeded(EventHandler<WorkerStateEvent> value) {
+        checkThread();
         getEventHelper().setOnSucceeded(value);
     }
 
@@ -419,6 +435,7 @@
      * @since JavaFX 2.1
      */
     public final ObjectProperty<EventHandler<WorkerStateEvent>> onCancelledProperty() {
+        checkThread();
         return getEventHelper().onCancelledProperty();
     }
 
@@ -430,6 +447,7 @@
      * @since JavaFX 2.1
      */
     public final EventHandler<WorkerStateEvent> getOnCancelled() {
+        checkThread();
         return eventHelper == null ? null : eventHelper.getOnCancelled();
     }
 
@@ -441,6 +459,7 @@
      * @since JavaFX 2.1
      */
     public final void setOnCancelled(EventHandler<WorkerStateEvent> value) {
+        checkThread();
         getEventHelper().setOnCancelled(value);
     }
 
@@ -461,6 +480,7 @@
      * @since JavaFX 2.1
      */
     public final ObjectProperty<EventHandler<WorkerStateEvent>> onFailedProperty() {
+        checkThread();
         return getEventHelper().onFailedProperty();
     }
 
@@ -472,6 +492,7 @@
      * @since JavaFX 2.1
      */
     public final EventHandler<WorkerStateEvent> getOnFailed() {
+        checkThread();
         return eventHelper == null ? null : eventHelper.getOnFailed();
     }
 
@@ -483,6 +504,7 @@
      * @since JavaFX 2.1
      */
     public final void setOnFailed(EventHandler<WorkerStateEvent> value) {
+        checkThread();
         getEventHelper().setOnFailed(value);
     }
 
@@ -503,6 +525,13 @@
     private Task<V> task;
 
     /**
+     * This boolean is set to true once the Service has been initially started. You can initialize
+     * the Service from any thread, and you can initially start it from any thread. But any
+     * subsequent usage of the service's methods must occur on the FX application thread.
+     */
+    private volatile boolean startedOnce = false;
+
+    /**
      * Create a new Service.
      */
     protected Service() {
@@ -653,11 +682,27 @@
         message.bind(task.messageProperty());
         title.bind(task.titleProperty());
 
-        // Advance the task to the "SCHEDULED" state
-        task.setState(State.SCHEDULED);
+        // Record that start has been called once, so we don't allow it to be called again from
+        // any thread other than the fx thread
+        startedOnce = true;
 
-        // Start the task
-        executeTask(task);
+        if (!isFxApplicationThread()) {
+            runLater(new Runnable() {
+                @Override public void run() {
+                    // Advance the task to the "SCHEDULED" state
+                    task.setState(State.SCHEDULED);
+
+                    // Start the task
+                    executeTask(task);
+                }
+            });
+        } else {
+            // Advance the task to the "SCHEDULED" state
+            task.setState(State.SCHEDULED);
+
+            // Start the task
+            executeTask(task);
+        }
     }
 
     /**
@@ -727,6 +772,7 @@
     public final <T extends Event> void addEventHandler(
             final EventType<T> eventType,
             final EventHandler<? super T> eventHandler) {
+        checkThread();
         getEventHelper().addEventHandler(eventType, eventHandler);
     }
 
@@ -745,6 +791,7 @@
     public final <T extends Event> void removeEventHandler(
             final EventType<T> eventType,
             final EventHandler<? super T> eventHandler) {
+        checkThread();
         getEventHelper().removeEventHandler(eventType, eventHandler);
     }
 
@@ -761,6 +808,7 @@
     public final <T extends Event> void addEventFilter(
             final EventType<T> eventType,
             final EventHandler<? super T> eventFilter) {
+        checkThread();
         getEventHelper().addEventFilter(eventType, eventFilter);
     }
 
@@ -779,6 +827,7 @@
     public final <T extends Event> void removeEventFilter(
             final EventType<T> eventType,
             final EventHandler<? super T> eventFilter) {
+        checkThread();
         getEventHelper().removeEventFilter(eventType, eventFilter);
     }
 
@@ -797,6 +846,7 @@
     protected final <T extends Event> void setEventHandler(
             final EventType<T> eventType,
             final EventHandler<? super T> eventHandler) {
+        checkThread();
         getEventHelper().setEventHandler(eventType, eventHandler);
     }
 
@@ -819,6 +869,7 @@
 
     @Override
     public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) {
+        checkThread();
         return getEventHelper().buildEventDispatchChain(tail);
     }
 
@@ -865,7 +916,7 @@
     protected abstract Task<V> createTask();
 
     void checkThread() {
-        if (!isFxApplicationThread()) {
+        if (startedOnce && !isFxApplicationThread()) {
             throw new IllegalStateException("Service must only be used from the FX Application Thread");
         }
     }
--- a/modules/graphics/src/main/java/javafx/concurrent/Task.java	Wed Oct 02 13:40:16 2013 -0700
+++ b/modules/graphics/src/main/java/javafx/concurrent/Task.java	Wed Oct 02 13:56:01 2013 -0700
@@ -99,7 +99,11 @@
  *     that every change to its public properties, as well as change notifications
  *     for state, errors, and for event handlers, all occur on the main JavaFX application
  *     thread. Accessing these properties from a background thread (including the
- *     {@link #call()} method) will result in runtime exceptions being raised.
+ *     {@link #call()} method) will result in runtime exceptions being raised. The only exception
+ *     to this, is when initially configuring a Task, which may safely be done
+ *     from any thread. However, once the Task has been initialized and
+ *     started, it may only thereafter be used from the FX thread (except for those methods clearly
+ *     marked as being appropriate for the subclass to invoke from the background thread).
  * </p>
  * <p>
  *     It is <strong>strongly encouraged</strong> that all Tasks be initialized with
@@ -639,10 +643,16 @@
     private AtomicReference<V> valueUpdate = new AtomicReference<>();
 
     /**
+     * This is used so we have a thread-safe way to ask whether the task was
+     * started in the checkThread() method.
+     */
+    private volatile boolean started = false;
+
+    /**
      * Creates a new Task.
      */
     public Task() {
-        this(new TaskCallable<V>());
+        this(new TaskCallable<>());
     }
 
     /**
@@ -675,7 +685,7 @@
      */
     protected abstract V call() throws Exception;
 
-    private ObjectProperty<State> state = new SimpleObjectProperty<State>(this, "state", State.READY);
+    private ObjectProperty<State> state = new SimpleObjectProperty<>(this, "state", State.READY);
     final void setState(State value) { // package access for the Service
         checkThread();
         final State s = getState();
@@ -725,6 +735,7 @@
      * @since JavaFX 2.1
      */
     public final ObjectProperty<EventHandler<WorkerStateEvent>> onScheduledProperty() {
+        checkThread();
         return getEventHelper().onScheduledProperty();
     }
 
@@ -736,6 +747,7 @@
      * @since JavaFX 2.1
      */
     public final EventHandler<WorkerStateEvent> getOnScheduled() {
+        checkThread();
         return eventHelper == null ? null : eventHelper.getOnScheduled();
     }
 
@@ -747,6 +759,7 @@
      * @since JavaFX 2.1
      */
     public final void setOnScheduled(EventHandler<WorkerStateEvent> value) {
+        checkThread();
         getEventHelper().setOnScheduled(value);
     }
 
@@ -768,6 +781,7 @@
      * @since JavaFX 2.1
      */
     public final ObjectProperty<EventHandler<WorkerStateEvent>> onRunningProperty() {
+        checkThread();
         return getEventHelper().onRunningProperty();
     }
 
@@ -779,6 +793,7 @@
      * @since JavaFX 2.1
      */
     public final EventHandler<WorkerStateEvent> getOnRunning() {
+        checkThread();
         return eventHelper == null ? null : eventHelper.getOnRunning();
     }
 
@@ -790,6 +805,7 @@
      * @since JavaFX 2.1
      */
     public final void setOnRunning(EventHandler<WorkerStateEvent> value) {
+        checkThread();
         getEventHelper().setOnRunning(value);
     }
 
@@ -811,6 +827,7 @@
      * @since JavaFX 2.1
      */
     public final ObjectProperty<EventHandler<WorkerStateEvent>> onSucceededProperty() {
+        checkThread();
         return getEventHelper().onSucceededProperty();
     }
 
@@ -822,6 +839,7 @@
      * @since JavaFX 2.1
      */
     public final EventHandler<WorkerStateEvent> getOnSucceeded() {
+        checkThread();
         return eventHelper == null ? null : eventHelper.getOnSucceeded();
     }
 
@@ -833,6 +851,7 @@
      * @since JavaFX 2.1
      */
     public final void setOnSucceeded(EventHandler<WorkerStateEvent> value) {
+        checkThread();
         getEventHelper().setOnSucceeded(value);
     }
 
@@ -854,6 +873,7 @@
      * @since JavaFX 2.1
      */
     public final ObjectProperty<EventHandler<WorkerStateEvent>> onCancelledProperty() {
+        checkThread();
         return getEventHelper().onCancelledProperty();
     }
 
@@ -865,6 +885,7 @@
      * @since JavaFX 2.1
      */
     public final EventHandler<WorkerStateEvent> getOnCancelled() {
+        checkThread();
         return eventHelper == null ? null : eventHelper.getOnCancelled();
     }
 
@@ -876,6 +897,7 @@
      * @since JavaFX 2.1
      */
     public final void setOnCancelled(EventHandler<WorkerStateEvent> value) {
+        checkThread();
         getEventHelper().setOnCancelled(value);
     }
 
@@ -897,6 +919,7 @@
      * @since JavaFX 2.1
      */
     public final ObjectProperty<EventHandler<WorkerStateEvent>> onFailedProperty() {
+        checkThread();
         return getEventHelper().onFailedProperty();
     }
 
@@ -908,6 +931,7 @@
      * @since JavaFX 2.1
      */
     public final EventHandler<WorkerStateEvent> getOnFailed() {
+        checkThread();
         return eventHelper == null ? null : eventHelper.getOnFailed();
     }
 
@@ -919,6 +943,7 @@
      * @since JavaFX 2.1
      */
     public final void setOnFailed(EventHandler<WorkerStateEvent> value) {
+        checkThread();
         getEventHelper().setOnFailed(value);
     }
 
@@ -932,12 +957,12 @@
      */
     protected void failed() { }
 
-    private final ObjectProperty<V> value = new SimpleObjectProperty<V>(this, "value");
+    private final ObjectProperty<V> value = new SimpleObjectProperty<>(this, "value");
     private void setValue(V v) { checkThread(); value.set(v); }
     @Override public final V getValue() { checkThread(); return value.get(); }
     @Override public final ReadOnlyObjectProperty<V> valueProperty() { checkThread(); return value; }
 
-    private final ObjectProperty<Throwable> exception = new SimpleObjectProperty<Throwable>(this, "exception");
+    private final ObjectProperty<Throwable> exception = new SimpleObjectProperty<>(this, "exception");
     private void _setException(Throwable value) { checkThread(); exception.set(value); }
     @Override public final Throwable getException() { checkThread(); return exception.get(); }
     @Override public final ReadOnlyObjectProperty<Throwable> exceptionProperty() { checkThread(); return exception; }
@@ -963,12 +988,12 @@
     @Override public final ReadOnlyBooleanProperty runningProperty() { checkThread(); return running; }
 
     private final StringProperty message = new SimpleStringProperty(this, "message", "");
-    @Override public final String getMessage() { return message.get(); }
-    @Override public final ReadOnlyStringProperty messageProperty() { return message; }
+    @Override public final String getMessage() { checkThread(); return message.get(); }
+    @Override public final ReadOnlyStringProperty messageProperty() { checkThread(); return message; }
 
     private final StringProperty title = new SimpleStringProperty(this, "title", "");
-    @Override public final String getTitle() { return title.get(); }
-    @Override public final ReadOnlyStringProperty titleProperty() { return title; }
+    @Override public final String getTitle() { checkThread(); return title.get(); }
+    @Override public final ReadOnlyStringProperty titleProperty() { checkThread(); return title; }
 
     @Override public final boolean cancel() {
         return cancel(true);
@@ -1189,9 +1214,8 @@
     /*
      * IMPLEMENTATION
      */
-
     private void checkThread() {
-        if (!isFxApplicationThread()) {
+        if (started && !isFxApplicationThread()) {
             throw new IllegalStateException("Task must only be used from the FX Application Thread");
         }
     }
@@ -1238,6 +1262,7 @@
     public final <T extends Event> void addEventHandler(
             final EventType<T> eventType,
             final EventHandler<? super T> eventHandler) {
+        checkThread();
         getEventHelper().addEventHandler(eventType, eventHandler);
     }
 
@@ -1256,6 +1281,7 @@
     public final <T extends Event> void removeEventHandler(
             final EventType<T> eventType,
             final EventHandler<? super T> eventHandler) {
+        checkThread();
         getEventHelper().removeEventHandler(eventType, eventHandler);
     }
 
@@ -1272,6 +1298,7 @@
     public final <T extends Event> void addEventFilter(
             final EventType<T> eventType,
             final EventHandler<? super T> eventFilter) {
+        checkThread();
         getEventHelper().addEventFilter(eventType, eventFilter);
     }
 
@@ -1290,6 +1317,7 @@
     public final <T extends Event> void removeEventFilter(
             final EventType<T> eventType,
             final EventHandler<? super T> eventFilter) {
+        checkThread();
         getEventHelper().removeEventFilter(eventType, eventFilter);
     }
 
@@ -1308,6 +1336,7 @@
     protected final <T extends Event> void setEventHandler(
             final EventType<T> eventType,
             final EventHandler<? super T> eventHandler) {
+        checkThread();
         getEventHelper().setEventHandler(eventType, eventHandler);
     }
 
@@ -1330,6 +1359,7 @@
 
     @Override
     public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) {
+        checkThread();
         return getEventHelper().buildEventDispatchChain(tail);
     }
 
@@ -1384,6 +1414,7 @@
             // in the SCHEDULED state and setting it again here has no negative
             // effect. But we must ensure that SCHEDULED is visited before RUNNING
             // in all cases so that developer code can be consistent.
+            task.started = true;
             task.runLater(new Runnable() {
                 @Override public void run() {
                     task.setState(State.SCHEDULED);
--- a/modules/graphics/src/main/java/javafx/geometry/Bounds.java	Wed Oct 02 13:40:16 2013 -0700
+++ b/modules/graphics/src/main/java/javafx/geometry/Bounds.java	Wed Oct 02 13:56:01 2013 -0700
@@ -30,7 +30,7 @@
  * other scene graph object. One interesting characteristic of a Bounds object
  * is that it may have a negative width, height, or depth. A negative value
  * for any of these indicates that the Bounds are "empty".
- * 
+ *
  * @since JavaFX 2.0
  */
 public abstract class Bounds {
--- a/modules/graphics/src/test/java/javafx/concurrent/AbstractTask.java	Wed Oct 02 13:40:16 2013 -0700
+++ b/modules/graphics/src/test/java/javafx/concurrent/AbstractTask.java	Wed Oct 02 13:56:01 2013 -0700
@@ -52,6 +52,7 @@
     public final Semaphore cancelledSemaphore = new Semaphore(0);
     public final Semaphore failedSemaphore = new Semaphore(0);
 
+    Thread appThread;
     ServiceTestBase test;
     
     // Simulates scheduling the concurrent for execution
@@ -61,7 +62,7 @@
 
     // For most tests, we want to pretend that we are on the FX app thread, always.
     @Override boolean isFxApplicationThread() {
-        return true;
+        return appThread == null || Thread.currentThread() == appThread;
     }
 
     // For most tests, we want to just run this stuff immediately
--- a/modules/graphics/src/test/java/javafx/concurrent/ServiceLifecycleTest.java	Wed Oct 02 13:40:16 2013 -0700
+++ b/modules/graphics/src/test/java/javafx/concurrent/ServiceLifecycleTest.java	Wed Oct 02 13:56:01 2013 -0700
@@ -29,7 +29,10 @@
 import javafx.beans.value.ObservableValue;
 import javafx.concurrent.mocks.MythicalEvent;
 import javafx.concurrent.mocks.SimpleTask;
+import javafx.event.Event;
 import javafx.event.EventHandler;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -384,7 +387,8 @@
     @Test public void workDonePropertyNotification() {
         final AtomicBoolean passed = new AtomicBoolean(false);
         service.workDoneProperty().addListener(new ChangeListener<Number>() {
-            @Override public void changed(ObservableValue<? extends Number> o,
+            @Override
+            public void changed(ObservableValue<? extends Number> o,
                                 Number oldValue, Number newValue) {
                 passed.set(newValue.doubleValue() == 10);
             }
@@ -413,7 +417,8 @@
     @Test public void totalWorkPropertyNotification() {
         final AtomicBoolean passed = new AtomicBoolean(false);
         service.totalWorkProperty().addListener(new ChangeListener<Number>() {
-            @Override public void changed(ObservableValue<? extends Number> o,
+            @Override
+            public void changed(ObservableValue<? extends Number> o,
                                 Number oldValue, Number newValue) {
                 passed.set(newValue.doubleValue() == 20);
             }
@@ -821,7 +826,8 @@
         executor.executeScheduled();
         task.complete();
         service.addEventHandler(WorkerStateEvent.WORKER_STATE_READY, new EventHandler<WorkerStateEvent>() {
-            @Override public void handle(WorkerStateEvent workerStateEvent) {
+            @Override
+            public void handle(WorkerStateEvent workerStateEvent) {
                 handlerCalled.set(true);
             }
         });
@@ -871,7 +877,8 @@
         service.addEventFilter(WorkerStateEvent.WORKER_STATE_READY, filter);
         service.removeEventFilter(WorkerStateEvent.WORKER_STATE_READY, filter);
         service.addEventFilter(WorkerStateEvent.WORKER_STATE_READY, new EventHandler<WorkerStateEvent>() {
-            @Override public void handle(WorkerStateEvent workerStateEvent) {
+            @Override
+            public void handle(WorkerStateEvent workerStateEvent) {
                 sanity.set(true);
             }
         });
@@ -930,7 +937,8 @@
             }
         });
         service.setOnScheduled(new EventHandler<WorkerStateEvent>() {
-            @Override public void handle(WorkerStateEvent workerStateEvent) {
+            @Override
+            public void handle(WorkerStateEvent workerStateEvent) {
                 filterCalledFirst.set(filterCalled.get());
             }
         });
@@ -945,7 +953,8 @@
     @Test public void scheduledCalledAfterHandler() {
         final AtomicBoolean handlerCalled = new AtomicBoolean(false);
         service.setOnScheduled(new EventHandler<WorkerStateEvent>() {
-            @Override public void handle(WorkerStateEvent workerStateEvent) {
+            @Override
+            public void handle(WorkerStateEvent workerStateEvent) {
                 handlerCalled.set(true);
             }
         });
@@ -1021,7 +1030,8 @@
         service.addEventFilter(WorkerStateEvent.WORKER_STATE_SCHEDULED, filter);
         service.removeEventFilter(WorkerStateEvent.WORKER_STATE_SCHEDULED, filter);
         service.addEventFilter(WorkerStateEvent.WORKER_STATE_SCHEDULED, new EventHandler<WorkerStateEvent>() {
-            @Override public void handle(WorkerStateEvent workerStateEvent) {
+            @Override
+            public void handle(WorkerStateEvent workerStateEvent) {
                 sanity.set(true);
             }
         });
@@ -1079,7 +1089,8 @@
             }
         });
         service.setOnRunning(new EventHandler<WorkerStateEvent>() {
-            @Override public void handle(WorkerStateEvent workerStateEvent) {
+            @Override
+            public void handle(WorkerStateEvent workerStateEvent) {
                 filterCalledFirst.set(filterCalled.get());
             }
         });
@@ -1109,7 +1120,8 @@
     @Test public void runningCalledAfterHandlerEvenIfConsumed() {
         final AtomicBoolean handlerCalled = new AtomicBoolean(false);
         service.setOnRunning(new EventHandler<WorkerStateEvent>() {
-            @Override public void handle(WorkerStateEvent workerStateEvent) {
+            @Override
+            public void handle(WorkerStateEvent workerStateEvent) {
                 handlerCalled.set(true);
                 workerStateEvent.consume();
             }
@@ -1170,7 +1182,8 @@
         service.addEventFilter(WorkerStateEvent.WORKER_STATE_RUNNING, filter);
         service.removeEventFilter(WorkerStateEvent.WORKER_STATE_RUNNING, filter);
         service.addEventFilter(WorkerStateEvent.WORKER_STATE_RUNNING, new EventHandler<WorkerStateEvent>() {
-            @Override public void handle(WorkerStateEvent workerStateEvent) {
+            @Override
+            public void handle(WorkerStateEvent workerStateEvent) {
                 sanity.set(true);
             }
         });
@@ -1325,7 +1338,8 @@
         service.addEventFilter(WorkerStateEvent.WORKER_STATE_SUCCEEDED, filter);
         service.removeEventFilter(WorkerStateEvent.WORKER_STATE_SUCCEEDED, filter);
         service.addEventFilter(WorkerStateEvent.WORKER_STATE_SUCCEEDED, new EventHandler<WorkerStateEvent>() {
-            @Override public void handle(WorkerStateEvent workerStateEvent) {
+            @Override
+            public void handle(WorkerStateEvent workerStateEvent) {
                 sanity.set(true);
             }
         });
@@ -1399,7 +1413,8 @@
     @Test public void cancelledCalledAfterHandler() {
         final AtomicBoolean handlerCalled = new AtomicBoolean(false);
         service.setOnCancelled(new EventHandler<WorkerStateEvent>() {
-            @Override public void handle(WorkerStateEvent workerStateEvent) {
+            @Override
+            public void handle(WorkerStateEvent workerStateEvent) {
                 handlerCalled.set(true);
             }
         });
@@ -1480,7 +1495,8 @@
         service.addEventFilter(WorkerStateEvent.WORKER_STATE_CANCELLED, filter);
         service.removeEventFilter(WorkerStateEvent.WORKER_STATE_CANCELLED, filter);
         service.addEventFilter(WorkerStateEvent.WORKER_STATE_CANCELLED, new EventHandler<WorkerStateEvent>() {
-            @Override public void handle(WorkerStateEvent workerStateEvent) {
+            @Override
+            public void handle(WorkerStateEvent workerStateEvent) {
                 sanity.set(true);
             }
         });
@@ -1592,7 +1608,8 @@
     @Test public void failedCalledAfterHandlerEvenIfConsumed() {
         final AtomicBoolean handlerCalled = new AtomicBoolean(false);
         service.setOnFailed(new EventHandler<WorkerStateEvent>() {
-            @Override public void handle(WorkerStateEvent workerStateEvent) {
+            @Override
+            public void handle(WorkerStateEvent workerStateEvent) {
                 handlerCalled.set(true);
             }
         });
@@ -1656,7 +1673,8 @@
         service.addEventFilter(WorkerStateEvent.WORKER_STATE_FAILED, filter);
         service.removeEventFilter(WorkerStateEvent.WORKER_STATE_FAILED, filter);
         service.addEventFilter(WorkerStateEvent.WORKER_STATE_FAILED, new EventHandler<WorkerStateEvent>() {
-            @Override public void handle(WorkerStateEvent workerStateEvent) {
+            @Override
+            public void handle(WorkerStateEvent workerStateEvent) {
                 sanity.set(true);
             }
         });
@@ -1669,10 +1687,662 @@
     }
 
     /***************************************************************************
-     *
-     * A mythical subclass should be able to set an event handler and
-     * have events fired on the Service work.
-     *
+     *                                                                         *
+     * Tests that invoking methods from the wrong thread leads to errors, and  *
+     * that regardless of which thread starts the Service, all notification    *
+     * (events, etc) happen on the FX thread only.                             *
+     *                                                                         *
+     **************************************************************************/
+
+    @Test public void canCreateServiceOnRandomThread() {
+        RandomThread random = new RandomThread(new Runnable() {
+            @Override public void run() {
+                DoNothingService s = null;
+                try {
+                    s = new DoNothingService();
+                } finally {
+                    if (s != null) s.shutdown();
+                }
+            }
+        });
+        random.test();
+    }
+
+    @Test public void canGetReferencesToPropertiesOnRandomThread() {
+        RandomThread random = new RandomThread(new Runnable() {
+            @Override public void run() {
+                DoNothingService s = null;
+                try {
+                    s = new DoNothingService();
+                    s.exceptionProperty();
+                    s.executorProperty();
+                    s.messageProperty();
+                    s.progressProperty();
+                    s.onCancelledProperty();
+                    s.onFailedProperty();
+                    s.onReadyProperty();
+                    s.onRunningProperty();
+                    s.onScheduledProperty();
+                    s.onSucceededProperty();
+                    s.runningProperty();
+                    s.stateProperty();
+                    s.titleProperty();
+                    s.totalWorkProperty();
+                    s.valueProperty();
+                    s.workDoneProperty();
+                } finally {
+                    if (s != null) s.shutdown();
+                }
+            }
+        });
+        random.test();
+    }
+
+    @Test public void canInvokeGettersOnRandomThread() {
+        RandomThread random = new RandomThread(new Runnable() {
+            @Override public void run() {
+                DoNothingService s = null;
+                try {
+                    s = new DoNothingService();
+                    s.getException();
+                    s.getExecutor();
+                    s.getMessage();
+                    s.getProgress();
+                    s.getOnCancelled();
+                    s.getOnFailed();
+                    s.getOnReady();
+                    s.getOnRunning();
+                    s.getOnScheduled();
+                    s.getOnSucceeded();
+                    s.isRunning();
+                    s.getState();
+                    s.getTitle();
+                    s.getTotalWork();
+                    s.getValue();
+                    s.getWorkDone();
+                } finally {
+                    if (s != null) s.shutdown();
+                }
+            }
+        });
+        random.test();
+    }
+
+    @Test public void canInvokeSettersOnRandomThread() {
+        RandomThread random = new RandomThread(new Runnable() {
+            @Override public void run() {
+                DoNothingService s = null;
+                try {
+                    s = new DoNothingService();
+                    s.setEventHandler(WorkerStateEvent.ANY, new EventHandler() {
+                        @Override
+                        public void handle(Event event) {
+                        }
+                    });
+                    s.setOnCancelled(new EventHandler<WorkerStateEvent>() {
+                        @Override
+                        public void handle(WorkerStateEvent event) {
+                        }
+                    });
+                    s.setOnFailed(new EventHandler<WorkerStateEvent>() {
+                        @Override
+                        public void handle(WorkerStateEvent event) {
+                        }
+                    });
+                    s.setOnReady(new EventHandler<WorkerStateEvent>() {
+                        @Override
+                        public void handle(WorkerStateEvent event) {
+                        }
+                    });
+                    s.setOnRunning(new EventHandler<WorkerStateEvent>() {
+                        @Override
+                        public void handle(WorkerStateEvent event) {
+                        }
+                    });
+                    s.setOnScheduled(new EventHandler<WorkerStateEvent>() {
+                        @Override
+                        public void handle(WorkerStateEvent event) {
+                        }
+                    });
+                    s.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
+                        @Override
+                        public void handle(WorkerStateEvent event) {
+                        }
+                    });
+                } finally {
+                    if (s != null) s.shutdown();
+                }
+            }
+        });
+        random.test();
+    }
+
+    @Test public void canInvokeStartOnRandomThread() {
+        RandomThread random = new RandomThread(new Runnable() {
+            @Override public void run() {
+                DoNothingService s = null;
+                try {
+                    s = new DoNothingService();
+                    s.start();
+                } finally {
+                    if (s != null) s.shutdown();
+                }
+            }
+        });
+        random.test();
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokeRestartOnRandomThreadAfterStart() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.restart();
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokeCancelOnRandomThreadAfterStart() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.cancel();
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokeSettersOnRandomThreadAfterStart_1() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.setEventHandler(WorkerStateEvent.ANY, new EventHandler() {
+                    @Override public void handle(Event event) { }
+                });
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokeSettersOnRandomThreadAfterStart_2() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.setOnCancelled(new EventHandler<WorkerStateEvent>() {
+                    @Override public void handle(WorkerStateEvent event) { }
+                });
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokeSettersOnRandomThreadAfterStart_3() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.setOnFailed(new EventHandler<WorkerStateEvent>() {
+                    @Override public void handle(WorkerStateEvent event) { }
+                });
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokeSettersOnRandomThreadAfterStart_4() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.setOnReady(new EventHandler<WorkerStateEvent>() {
+                    @Override public void handle(WorkerStateEvent event) { }
+                });
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokeSettersOnRandomThreadAfterStart_5() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.setOnRunning(new EventHandler<WorkerStateEvent>() {
+                    @Override public void handle(WorkerStateEvent event) { }
+                });
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokeSettersOnRandomThreadAfterStart_6() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.setOnScheduled(new EventHandler<WorkerStateEvent>() {
+                    @Override public void handle(WorkerStateEvent event) { }
+                });
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokeSettersOnRandomThreadAfterStart_7() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
+                    @Override public void handle(WorkerStateEvent event) { }
+                });
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokeGettersOnRandomThreadAfterStart_1() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.getException();
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokeGettersOnRandomThreadAfterStart_2() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.getExecutor();
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokeGettersOnRandomThreadAfterStart_3() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.getMessage();
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokeGettersOnRandomThreadAfterStart_4() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.getProgress();
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokeGettersOnRandomThreadAfterStart_5() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.getOnCancelled();
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokeGettersOnRandomThreadAfterStart_6() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.getOnFailed();
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokeGettersOnRandomThreadAfterStart_7() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.getOnReady();
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokeGettersOnRandomThreadAfterStart_8() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.getOnRunning();
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokeGettersOnRandomThreadAfterStart_9() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.getOnScheduled();
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokeGettersOnRandomThreadAfterStart_10() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.getOnSucceeded();
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokeGettersOnRandomThreadAfterStart_11() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.isRunning();
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokeGettersOnRandomThreadAfterStart_12() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.getState();
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokeGettersOnRandomThreadAfterStart_13() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.getTitle();
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokeGettersOnRandomThreadAfterStart_14() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.getTotalWork();
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokeGettersOnRandomThreadAfterStart_15() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.getValue();
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokeGettersOnRandomThreadAfterStart_16() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.getValue();
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokePropertyGettersOnRandomThreadAfterStart_1() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.exceptionProperty();
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokePropertyGettersOnRandomThreadAfterStart_2() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.executorProperty();
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokePropertyGettersOnRandomThreadAfterStart_3() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.messageProperty();
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokePropertyGettersOnRandomThreadAfterStart_4() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.progressProperty();
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokePropertyGettersOnRandomThreadAfterStart_5() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.onCancelledProperty();
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokePropertyGettersOnRandomThreadAfterStart_6() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.onFailedProperty();
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokePropertyGettersOnRandomThreadAfterStart_7() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.onReadyProperty();
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokePropertyGettersOnRandomThreadAfterStart_8() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.onRunningProperty();
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokePropertyGettersOnRandomThreadAfterStart_9() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.onScheduledProperty();
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokePropertyGettersOnRandomThreadAfterStart_10() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.onSucceededProperty();
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokePropertyGettersOnRandomThreadAfterStart_11() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.runningProperty();
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokePropertyGettersOnRandomThreadAfterStart_12() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.stateProperty();
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokePropertyGettersOnRandomThreadAfterStart_13() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.titleProperty();
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokePropertyGettersOnRandomThreadAfterStart_14() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.totalWorkProperty();
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokePropertyGettersOnRandomThreadAfterStart_15() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.valueProperty();
+            }
+        });
+    }
+
+    @Test (expected = IllegalStateException.class)
+    public void cannotInvokePropertyGettersOnRandomThreadAfterStart_16() throws Throwable {
+        assertThrowsException(new ServiceTestExecution() {
+            @Override public void test(DoNothingService s) {
+                s.workDoneProperty();
+            }
+        });
+    }
+
+    private void assertThrowsException(final ServiceTestExecution c) throws Throwable {
+        RandomThread random = new RandomThread(new Runnable() {
+            @Override public void run() {
+                DoNothingService s = null;
+                try {
+                    s = new DoNothingService();
+                    s.start();
+                    c.test(s);
+                } finally {
+                    if (s != null) s.shutdown();
+                }
+            }
+        });
+
+        try {
+            random.test();
+        } catch (AssertionError er) {
+            throw er.getCause();
+        }
+    }
+
+    private interface ServiceTestExecution {
+        public void test(DoNothingService s);
+    }
+
+    /**
+     * Specialized thread used for checking access to various methods from a "random thread" other
+     * than the FX thread. This class has built into it all the supported needed for handling
+     * exceptions and so forth, such that assertion errors are raised if an exception occurs
+     * on the thread, and also handles blocking until the thread body concludes.
+     */
+    private static final class RandomThread extends Thread {
+        private final CountDownLatch testCompleted = new CountDownLatch(1);
+        private Throwable error;
+
+        public RandomThread(Runnable target) {
+            super(target);
+        }
+
+        @Override public void run() {
+            try {
+                super.run();
+            } catch (Throwable th) {
+                error = th;
+            } finally {
+                testCompleted.countDown();
+            }
+        }
+
+        public void test() throws AssertionError {
+            start();
+            try {
+                testCompleted.await();
+            } catch (InterruptedException e) {
+                throw new AssertionError("Test did not complete normally");
+            }
+            if (error != null) {
+                throw new AssertionError(error);
+            }
+        }
+    }
+
+    /**
+     * A service which does absolutely nothing and isn't hardwired to believe that
+     * the test thread is the FX thread (unlike the other services in these tests)
+     */
+    private static final class DoNothingService extends Service {
+        private Thread pretendFXThread;
+        private ConcurrentLinkedQueue<Runnable> eventQueue = new ConcurrentLinkedQueue<>();
+        private volatile boolean shutdown = false;
+
+        public DoNothingService() {
+            setExecutor(new Executor() {
+                @Override public void execute(Runnable command) {
+                    Thread backgroundThread = new Thread(command);
+                    backgroundThread.start();
+                }
+            });
+        }
+
+        void shutdown() {
+            shutdown = true;
+        }
+
+        @Override protected Task createTask() {
+            return new Task() {
+                @Override protected Object call() throws Exception {
+                    return null;
+                }
+
+                @Override boolean isFxApplicationThread() {
+                    return Thread.currentThread() == pretendFXThread;
+                }
+
+                @Override
+                void runLater(Runnable r) {
+                    DoNothingService.this.runLater(r);
+                }
+            };
+        }
+
+        @Override void runLater(Runnable r) {
+            eventQueue.add(r);
+            if (pretendFXThread == null) {
+                pretendFXThread = new Thread() {
+                    @Override public void run() {
+                        while (!shutdown) {
+                            Runnable event = eventQueue.poll();
+                            if (event != null) {
+                                event.run();
+                            }
+                        }
+                    }
+                };
+                pretendFXThread.start();
+            }
+        }
+
+        @Override boolean isFxApplicationThread() {
+            return Thread.currentThread() == pretendFXThread;
+        }
+    }
+
+    /***************************************************************************
+     *                                                                         *
+     * A mythical subclass should be able to set an event handler and          *
+     * have events fired on the Service work.                                  *
+     *                                                                         *
      **************************************************************************/
 
     @Test public void eventFiredOnSubclassWorks() {
@@ -1708,5 +2378,9 @@
         }
 
         @Override void checkThread() { }
+
+        @Override void runLater(Runnable r) {
+            r.run();
+        }
     }
 }
--- a/modules/graphics/src/test/java/javafx/concurrent/ServiceTest.java	Wed Oct 02 13:40:16 2013 -0700
+++ b/modules/graphics/src/test/java/javafx/concurrent/ServiceTest.java	Wed Oct 02 13:56:01 2013 -0700
@@ -25,15 +25,19 @@
 
 package javafx.concurrent;
 
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.concurrent.mocks.SimpleTask;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicBoolean;
-import javafx.beans.property.ObjectProperty;
-import javafx.beans.property.SimpleObjectProperty;
-import javafx.concurrent.mocks.SimpleTask;
 import org.junit.Before;
 import org.junit.Test;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
 
 /**
  */
@@ -49,6 +53,9 @@
             }
 
             @Override void checkThread() { }
+            @Override void runLater(Runnable r) {
+                r.run();
+            }
         };
     }
 
@@ -171,6 +178,7 @@
         for (int i=0; i<32; i++) {
             Service<Void> s = new Service<Void>() {
                 @Override void checkThread() { }
+                @Override void runLater(Runnable r) { r.run(); }
 
                 @Override protected Task<Void> createTask() {
                     return new Task<Void>() {
--- a/modules/graphics/src/test/java/javafx/concurrent/TestServiceFactory.java	Wed Oct 02 13:40:16 2013 -0700
+++ b/modules/graphics/src/test/java/javafx/concurrent/TestServiceFactory.java	Wed Oct 02 13:56:01 2013 -0700
@@ -45,6 +45,7 @@
             @Override protected Task<String> createTask() {
                 currentTask = createTestTask();
                 currentTask.test = test;
+                currentTask.appThread = appThread;
                 return currentTask;
             }
 
@@ -56,7 +57,7 @@
                 if (test != null) {
                     test.eventQueue.add(r);
                 } else {
-                    super.runLater(r);
+                    r.run();
                 }
             }
         };