IllegalConcurrentAccessException thrown when starting BackgroundTask sequentially

For file uploads we chose to use a BackgroundTask to display a progress bar. You can upload a file by pressing the “Upload” button on the “DataModelBrowse” screen:

  1. By choosing a file and pressing “Upload” it triggers the method “uploadPlan()” which imports the Data from the file
  2. “uploadPlan()” opens the Screen “ImportPreview” where the imported data can be validated by the user
  3. By pressing “Ok” on the “ImportPreview” screen the imported data gets persisted to the database

Please note that I use “BackgroundWorkWindow.show()” to start both the first BackgroundTask in DataModelBrowse and the second BackgroundTask in “ImportPreview”:

DataModelBrowse:

private void uploadPlan(FileUploadDialog dialog, Map<Class, EEntity> defaultEntities) {
    UUID fileId = dialog.getFileId();
    String fileName = dialog.getFileName();

    if (!fileName.toLowerCase().endsWith(".xls") && !fileName.toLowerCase().endsWith(".xlsx")) {
    getWindow().showMessageDialog(messages.getMainMessage("error_title_import"), messages.getMainMessage("error_unknown_filetype"),
            Frame.MessageType.WARNING.modal(true).closeOnClickOutside(true));
    return;
    }
	
    FileDescriptor fileDescriptor = fileUploadingAPI.getFileDescriptor(fileId, fileName);

    BackgroundWorkWindow.show(new BackgroundTask<Object, Object>(60, this) {
	
    @Override
    public Object run(TaskLifeCycle<Object> taskLifeCycle) throws Exception {
        Object result = importService.importPlan(fileUploadingAPI.getFile(fileId), fileDescriptor, defaultEntities);
        return result;
    }
		
    @Override
    public void done(Object result) {
        if (result instanceof ImportResult) {
				// Successful import
            ImportResult importResult = (ImportResult) result;
            screenBuilders.editor(DataModel.class, this.getOwnerScreen())
                    .editEntity(importResult.getDataModel())
                    .withScreenClass(ImportPreview.class)
                    .withOptions(new ImportPreview.Options(importResult, fileId, fileDescriptor))
                    .withAfterCloseListener(afterCloseEvent -> {
                        if (afterCloseEvent.getCloseAction().equals(WINDOW_COMMIT_AND_CLOSE_ACTION)) {
                            dataModelDl.load();
                        }
                    })
                    .withLaunchMode(OpenMode.DIALOG)
                    .build()
                    .show();
        } else {
            // result is null or wrong type
            getWindow().showMessageDialog(messages.getMainMessage("error_title_import"), messages.getMainMessage("error_loading_file"),
                    Frame.MessageType.WARNING.modal(true).closeOnClickOutside(true));

        }
    }
}, messages.getMainMessage("uploadProgressTitle"),     messages.getMainMessage("uploadProgressText"), false);
}

ImportPreview:

@Subscribe("okBtn")
public OperationResult onOkBtnClick(Button.ClickEvent event) {

ValidationErrors validationErrors = screenValidation.validateUiComponents((ComponentContainer) this.getWindow().getFrame().getComponent("form"));
	
if (!validationErrors.isEmpty()) {
    ScreenValidation screenValidation = getBeanLocator().get(ScreenValidation.class);
    screenValidation.showValidationErrors(this, validationErrors);
    return OperationResult.fail();
} else {
    BackgroundWorkWindow.show(new BackgroundTask<Object, Object>(600, this
            .getWindow().getFrameOwner()) {
				
        @Override
        public Object run(TaskLifeCycle<Object> taskLifeCycle) throws Exception {
            // get entries from import and commit 
            return importDatabaseService.commit(options.getImportResult().getEntities());
        }
			
    }, messages.getMainMessage("uploadProgressTitle"), messages.getMainMessage("uploadProgressText"), false);
		
    closeWithDiscard();
    return OperationResult.success();
}}

This mechanism works perfectly fine when uploading the first file. However if you try to upload multiple files sequentially it wont work. Instead an IllegalConcurrentAccessException is thrown:
“UI Shared state was accessed from a background thread”

The documentation states that this Exception occours whenever one tries to read/change UI components from the background task. I can’t figure out why it happens in my code. Maybe the exception is related to the fact that I start a BackgroundTask within a BackgroundTask? Thanks in advance.

Hi,
Your run() methods look correct. They don’t access anything they shouldn’t.
Can you show full exception stack trace? It contains information useful for debugging.

Also I don’t understand, if you “upload multiple files sequentially” - how your logic is organized? Do you expect multiple dialogs to be opened simultaneously, or sequentially one by one?

I don’t expect several files to be uploaded simultaneously. With sequentially I mean that the files get uploaded one by one.
There will always be one file uploaded at the time with a single dialog opened.

Stacktrace:

14:18:22.237 ERROR c.h.c.gui.executors.BackgroundWorker    - Exception occurred in background task
com.haulmont.cuba.gui.executors.IllegalConcurrentAccessException: UI Shared state was accessed from a background thread
	at com.haulmont.cuba.web.App.lambda$static$695e4c50$1(App.java:92) ~[cuba-web-7.2.13.jar:7.2.13]
	at com.vaadin.server.AbstractClientConnector.markAsDirty(AbstractClientConnector.java:162) ~[vaadin-server-8.9.2-19-cuba.jar:8.9.2-19-cuba]
	at com.vaadin.v7.ui.Table.markAsDirty(Table.java:1859) ~[vaadin-compatibility-server-8.9.2-19-cuba.jar:8.9.2-19-cuba]
	at com.vaadin.v7.ui.Table.refreshRenderedCells(Table.java:1768) ~[vaadin-compatibility-server-8.9.2-19-cuba.jar:8.9.2-19-cuba]
	at com.haulmont.cuba.web.widgets.CubaGroupTable.refreshRenderedCells(CubaGroupTable.java:751) ~[cuba-web-widgets-7.2.13.jar:na]
	at com.vaadin.v7.ui.Table.refreshRowCache(Table.java:2670) ~[vaadin-compatibility-server-8.9.2-19-cuba.jar:8.9.2-19-cuba]
	at com.haulmont.cuba.web.widgets.CubaTable.refreshRowCache(CubaTable.java:1132) ~[cuba-web-widgets-7.2.13.jar:na]
	at com.vaadin.v7.ui.Table.valueChange(Table.java:4247) ~[vaadin-compatibility-server-8.9.2-19-cuba.jar:8.9.2-19-cuba]
	at com.haulmont.cuba.web.gui.components.table.TableDataContainer.datasourceValueChanged(TableDataContainer.java:294) ~[cuba-web-7.2.13.jar:7.2.13]
	at com.haulmont.bali.events.EventHub.publish(EventHub.java:170) ~[cuba-global-7.2.13.jar:7.2.13]
	at com.haulmont.cuba.gui.components.data.table.ContainerTableItems.containerItemPropertyChanged(ContainerTableItems.java:98) ~[cuba-gui-7.2.13.jar:7.2.13]
	at com.haulmont.bali.events.EventHub.publish(EventHub.java:170) ~[cuba-global-7.2.13.jar:7.2.13]
	at com.haulmont.cuba.gui.model.impl.InstanceContainerImpl.itemPropertyChanged(InstanceContainerImpl.java:178) ~[cuba-gui-7.2.13.jar:7.2.13]
	at com.haulmont.chile.core.model.impl.AbstractInstance.propertyChanged(AbstractInstance.java:57) ~[cuba-global-7.2.13.jar:7.2.13]
	at de.importing.ImportResult.lambda$getEntities$2(ImportResult.java:165) ~[importing.jar:na]
	at java.util.ArrayList.forEach(ArrayList.java:1259) ~[na:1.8.0_282]
	at de.importing.ImportResult.getEntities(ImportResult.java:164) ~[importing.jar:na]
	at de.importing.ImportPreview$1.run(ImportPreview.java:124) ~[importing.jar:na]
	at com.haulmont.cuba.gui.backgroundwork.LocalizedTaskWrapper.run(LocalizedTaskWrapper.java:57) ~[cuba-gui-7.2.13.jar:7.2.13]
	at com.haulmont.cuba.web.gui.executors.impl.WebBackgroundWorker$WebTaskExecutor.call(WebBackgroundWorker.java:219) ~[cuba-web-7.2.13.jar:7.2.13]
	at java.util.concurrent.FutureTask.run(FutureTask.java:266) ~[na:1.8.0_282]
	at com.haulmont.cuba.web.gui.executors.impl.WebBackgroundWorker$WebTaskExecutor.lambda$startExecution$1(WebBackgroundWorker.java:390) ~[cuba-web-7.2.13.jar:7.2.13]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) ~[na:1.8.0_282]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) ~[na:1.8.0_282]
	at java.lang.Thread.run(Thread.java:748) ~[na:1.8.0_282]

The error is here.
In the ImportResult.lambda$getEntities$2 you modify some entity which is displayed in the table. It triggers property changed listener and causes table repaint.
You shouldn’t modify entities that are bound to UI components inside of run() method.

1 Like

Thank you very much. I did the following and it works now as expected:

Before

    BackgroundWorkWindow.show(new BackgroundTask<Object, Object>(600, this
        .getWindow().getFrameOwner()) {
			
        @Override
        public Object run(TaskLifeCycle<Object> taskLifeCycle) throws Exception {
            // get entries from import and commit 
            return importDatabaseService.commit(options.getImportResult().getEntities());
        }
		
    }, messages.getMainMessage("uploadProgressTitle"), messages.getMainMessage("uploadProgressText"), false);

After

    List<Entity> entities = options.getImportResult().getEntities();

    BackgroundWorkWindow.show(new BackgroundTask<Object, Object>(600, this
        .getWindow().getFrameOwner()) {
			
        @Override
        public Object run(TaskLifeCycle<Object> taskLifeCycle) throws Exception {
            // get entries from import and commit 
            return importDatabaseService.commit(entities);
        }
		
    }, messages.getMainMessage("uploadProgressTitle"), messages.getMainMessage("uploadProgressText"), false);