eaglei22
eaglei22

Reputation: 2831

RequestCycle goes null when using a separate thread

I am using a separate thread to handle the processing of a file. The main thread holds the table to display to the user using a listView, and uses an AjaxSelfUpdatingTimer to refresh the list every second.

The problem is, after processing about 100 lines in my CSV file, I keep getting a no requestCyle exception:

Exception in thread "Thread-12" org.apache.wicket.WicketRuntimeException: No RequestCycle is currently set!
    at org.apache.wicket.Component.getRequest(Component.java:1804)
    at org.apache.wicket.markup.html.WebPage.dirty(WebPage.java:318)
    at org.apache.wicket.Page.dirty(Page.java:249)
    at org.apache.wicket.Page.componentStateChanging(Page.java:926)
    at org.apache.wicket.Component.addStateChange(Component.java:3528)
    at org.apache.wicket.Component.error(Component.java:1225)
    at com.wicket.BulkLoadPage$BatchLoaderProcessingThread.processLine(BulkLoadPage.java:806)
    at com.wicket.BulkLoadPage$BatchLoaderProcessingThread.run(BulkLoadPage.java:674)
    at java.lang.Thread.run(Thread.java:662)

these are my runnable classes the thread calls:

class BatchLoaderProcessingThread implements Runnable
{
    @Override
    public void run()
    {
        processLine();
        loaderFinished();
    }   

    public void cancelThread()
    {
        cancelLoaderThread = true;
    }   
}       

class BatchDeleteProcessingThread implements Runnable
{
    @Override
    public void run()
    {
        processLine();
        deleterFinished();
    }   

    public void cancelThread()
    {
        cancelDeleterThread = true;
    }   
}

I don't understand why the requestCycle would just go null.. How can I prevent this from happening?

Edit:

Commenting out feedback message do troubleshoot requestRecycle error, I receive this error:

    java.io.IOException: Read error
at java.io.FileInputStream.readBytes(Native Method)
at java.io.FileInputStream.read(FileInputStream.java:220)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:264)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:306)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:158)
at java.io.InputStreamReader.read(InputStreamReader.java:167)
at java.io.BufferedReader.read1(BufferedReader.java:185)
at java.io.BufferedReader.read(BufferedReader.java:261)
at java.io.BufferedReader.fill(BufferedReader.java:136)

    Dec 30 13:14:31 ERROR BulkLoadPage-java.io.IOException: Read error
at java.io.BufferedReader.readLine(BufferedReader.java:299)
at java.io.BufferedReader.readLine(BufferedReader.java:362)
at au.com.bytecode.opencsv.CSVReader.getNextLine(CSVReader.java:266)
at au.com.bytecode.opencsv.CSVReader.readNext(CSVReader.java:233)
at com..wicket.BulkLoadPage.processLine(BulkLoadPage.java:547)
at com..wicket.BulkLoadPage.access$0(BulkLoadPage.java:532)
at     

    com..wicket.BulkLoadPage$BatchLoaderProcessingThread.run(BulkLoadPage.java:1294)
at java.lang.Thread.run(Thread.java:662)

this error only occurs with the larger file as well. All of the lines inside the csv are duplicate only to mimic a large file.. so all lines are the same, and there shouldn't be an error caused directly from the file. Is there something else that I should be looking for that would cause this error from using another thread?

Upvotes: 0

Views: 2040

Answers (3)

Martin Strejc
Martin Strejc

Reputation: 4347

RequestCycle is a thread local singleton. If you're running a process in another thread that really means the RequestCycle singleton doesn't exist in your new thread.

The following example is placed on https://repo.twinstone.org/projects/WISTF/repos/wicket-examples-1.4/browse

The idea is about holding the reference to the current RequestCycle, you cannot call RequestCycle.get() due to the thread local singleton doesn't exist in any other thread. This CustomRequestCycle implementation is ever waiting until the new thread notify its finishing. The counter of loops is just a protection to do not freeze the primary thread if the WaitingRunnable stops working/freezes.

RESULTS oF TESTS:

Check your logs for case 1, the separate thread is finishing very soon, RequestCycle doesn't wait for detaching

http://localhost:8080/wicket-examples14/wait?millis=10
DEBUG - CustomRequestCycle         - Waiting until notify: 0
INFO  - WaitingRunnableNotifier    - Separate thread waiting finished cz.wicketstuff.enteam.wicket.examples14.request.ThreadWaitingPage
INFO  - CustomRequestCycle         - Notifier returned: Successfully finished

Check your logs for case 2, the separate thread is finishing in time, RequestCycle has to wait for detaching

http://localhost:8080/wicket-examples14/wait?millis=3000
DEBUG - CustomRequestCycle         - Waiting until notify: 0
DEBUG - CustomRequestCycle         - Waiting until notify: 1
DEBUG - CustomRequestCycle         - Waiting until notify: 2
INFO  - WaitingRunnableNotifier    - Separate thread waiting finished cz.wicketstuff.enteam.wicket.examples14.request.ThreadWaitingPage
INFO  - CustomRequestCycle         - Notifier returned: Successfully finished

Check your logs for case 3, the separate thread is finishing on time, RequestCycle is already detached

http://localhost:8080/wicket-examples14/wait?millis=10000
DEBUG - CustomRequestCycle         - Waiting until notify: 0
DEBUG - CustomRequestCycle         - Waiting until notify: 1
DEBUG - CustomRequestCycle         - Waiting until notify: 2
DEBUG - CustomRequestCycle         - Waiting until notify: 3
DEBUG - CustomRequestCycle         - Waiting until notify: 4
DEBUG - CustomRequestCycle         - Waiting until notify: 5
DEBUG - CustomRequestCycle         - Waiting until notify: 6
DEBUG - CustomRequestCycle         - Waiting until notify: 7
INFO  - CustomRequestCycle         - Notifier returned: null
INFO  - WaitingRunnableNotifier    - Separate thread waiting finished cz.wicketstuff.enteam.wicket.examples14.request.ThreadWaitingPage

SOURCES:

WicketApplication

@Override
public RequestCycle newRequestCycle(Request request, Response response) {
    return new CustomRequestCycle(this, (WebRequest)request, (WebResponse)response);
}

CustomRequestCycle

public class CustomRequestCycle extends WebRequestCycle implements INotifier<String> {

    private static final Logger log = LoggerFactory.getLogger(CustomRequestCycle.class);

    private long sleepTime = 1000L;
    private long maxLoops = 8;

    private boolean canDetach = true;
    private String notifierResult;

    public CustomRequestCycle(WicketApplication application, WebRequest request,
            Response response) {
        super(application, request, response);
    }

    public void notifyAny(String payload) {
        notifierResult = payload;
        canDetach = true;
    }

    @Override
    public void detach() {
        long counter = 0;
        while(!canDetach && maxLoops > counter) {
            log.debug("Waiting until notify: " + counter);
            try {
                Thread.sleep(sleepTime);
            } catch (InterruptedException e) {
                // do nothing
            }
            counter++;
        }
        log.info("Notifier returned: " + notifierResult);
        super.detach();
    }

    public static CustomRequestCycle get() {
        return (CustomRequestCycle)RequestCycle.get();
    }

    /**
     * @return the canDetach
     */
    public boolean isCanDetach() {
        return canDetach;
    }

    /**
     * @param canDetach the canDetach to set
     */
    public void setCanDetach(boolean canDetach) {
        this.canDetach = canDetach;
    }


}

WaitingRunnableNotifier

public class WaitingRunnableNotifier implements Runnable {

    private static final Logger log = LoggerFactory.getLogger(WaitingRunnableNotifier.class);

    private final long waitTime;

    private RequestCycle requestCycle;
    private INotifier<String> notifier;

    public WaitingRunnableNotifier(RequestCycle requestCycle, long waitTime, INotifier<String> notifier) {
        super();
        this.notifier = notifier;
        this.requestCycle = requestCycle;
        this.waitTime = waitTime;
    }

    public void run() {
        String message = null;
        try {
            try {
                Thread.sleep(waitTime);
            } catch (InterruptedException e) {
            }           
            log.info("Separate thread waiting finished " + requestCycle.getResponsePageClass().getCanonicalName());
            message = "Successfully finished";
        } catch (Exception e) {
            log.error("Exception during WaitingRunnableNotifier.run()", e);
            message = "Exception: " + e.getMessage();
        } finally {
            notifier.notifyAny(message);
            clean();
        }       
    }

    /**
     * Clean object references
     */
    private void clean() {
        requestCycle = null;
        notifier = null;        
    }   

}

ThreadWaitingPage is a page with parameter 'millis'. There you can invoke the another thread and wait unitl it is finished.

public class ThreadWaitingPage extends WebPage {

    private static final long serialVersionUID = 1L;

    private final long millis;

    public ThreadWaitingPage(final PageParameters parameters) {
        super(parameters);
        millis = parameters.getLong("millis");
        add(new Label("millis", String.valueOf(millis)));
    }

    @Override
    protected void onInitialize() {
        super.onInitialize();
        CustomRequestCycle requestCycle = CustomRequestCycle.get();
        requestCycle.setCanDetach(false);
        new Thread(new WaitingRunnableNotifier(requestCycle, millis, requestCycle)).start();

    }

}

Upvotes: 1

eaglei22
eaglei22

Reputation: 2831

I have temporarily fixed this issue by first loading the lines into a string ArrayList in the main thread and just read from that list inside the thread..

I don't really see any latency issues, so maybe this can be considered a good alternative? If it isn't please let me know, otherwise hopefully this helps someone else some day who struggles with this.

I have also posted my issue here: http://apache-wicket.1842946.n4.nabble.com/MultiThreading-issues-with-Wicket-td4663325.html#a4663389

and was given some good resource links to review:

Multithreading in wicket:

http://javathoughts.capesugarbird.com/2008/04/spawning-thread-for-lengthy-operation.html

RequestCycle: http://wicket.apache.org/guide/guide/chapter8.html

Upvotes: 1

Martin Cassidy
Martin Cassidy

Reputation: 736

Ideally, you shouldn't be creating your own threads, the container should be left to do all that for you. Of course, this means you can't leave bulk processes running in the background between requests.

I've done something similar before (breaking the rule I just mentioned!) and the easiest way is to keep Wicket out of your own threads. If your threads populate a List for example, and your ajax timer callback extracts the records from the list and adds them to the list view.

The problem with list views though is you have to add the parent component to the ajax request target, meaning that the whole list view will be sent back in the ajax response, not just the new entries you've added but all the old ones as well, which would defeat the point of loading them incrementally.

As its only a CSV file, it might be better for you to use a data table, and write a data provider to open the file and read only the records for that request which should be much simpler.

Upvotes: 1

Related Questions