Skip to main content


Eclipse Community Forums
Forum Search:

Search      Help    Register    Login    Home
Home » Modeling » EMF "Technology" (Ecore Tools, EMFatic, etc)  » [EMFStore] Updates and commits with concurrent users
[EMFStore] Updates and commits with concurrent users [message #1290931] Thu, 10 April 2014 15:11 Go to next message
Scott Dybiec is currently offline Scott DybiecFriend
Messages: 148
Registered: July 2009
Senior Member
My application needs a two-way sync capability in a multi-user (~10
users) environment sharing a common project, but I'm not sure the best
way to implement this. So far I naively implemented 'sync' as a
combination of EMFStore's update() and commit() operations, and I'm
struggling to avoid a race condition I've encountered during testing.

Since update() and commit() are separate operations, there is room for
another client (Client 2) to commit() in between Client 1's update() and
commit() operations. update() and commit() are separate but sometimes
dependent on one another and commits to the server can only be applied
once the client and server have a common version of the model -- which
may require an update from the server. Depending on the ordering of
these interweaved update() and commit() calls to the server from
concurrent clients, I've found that exceptions can be thrown that are
hard to recover from as there is no limit to the number times the
exceptions could recur.

Here's the scenario I've encountered using EMFStore's example merge
application to demonstrate how these "race conditions" occur. Since any
of the users can execute a commit() at any point, I've inserted a method
call at various points to simulate the effects of other clients
committing to the shared project

changeAndCommit(ESLocalProject demoProject)

to commit changes to the shared project on the server in between the
update, commit and commit retry for the demoProjectCopy.

Is there a coding pattern or example code I should adopt to implement a
two-way sync capability? Is there any locking feature in EMFStore that I
could use to eliminate the potential for the interweaving of update()
and commit() calls to the server from concurrent clients? Or perhaps
some other way to simulate an atomic sync function?

Here's the modified merge example code demonstrating the race condition:

package org.eclipse.emf.emfstore.example.merging;

import java.io.File;
import java.io.IOException;
import java.util.List;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.emf.emfstore.bowling.BowlingFactory;
import org.eclipse.emf.emfstore.bowling.League;
import org.eclipse.emf.emfstore.bowling.Player;
import org.eclipse.emf.emfstore.client.ESLocalProject;
import org.eclipse.emf.emfstore.client.ESServer;
import org.eclipse.emf.emfstore.client.ESWorkspace;
import org.eclipse.emf.emfstore.client.ESWorkspaceProvider;
import org.eclipse.emf.emfstore.client.callbacks.ESUpdateCallback;
import
org.eclipse.emf.emfstore.client.exceptions.ESServerStartFailedException;
import org.eclipse.emf.emfstore.common.ESSystemOutProgressMonitor;
import
org.eclipse.emf.emfstore.common.model.ESModelElementIdToEObjectMapping;
import org.eclipse.emf.emfstore.internal.client.model.Configuration;
import
org.eclipse.emf.emfstore.internal.client.model.exceptions.ChangeConflictException;
import org.eclipse.emf.emfstore.internal.common.model.util.FileUtil;
import org.eclipse.emf.emfstore.internal.server.ServerConfiguration;
import org.eclipse.emf.emfstore.server.ESConflict;
import org.eclipse.emf.emfstore.server.ESConflictSet;
import org.eclipse.emf.emfstore.server.exceptions.ESException;
import org.eclipse.emf.emfstore.server.exceptions.ESUpdateRequiredException;
import org.eclipse.emf.emfstore.server.model.ESChangePackage;
import org.eclipse.emf.emfstore.server.model.versionspec.ESVersionSpec;
import org.eclipse.equinox.app.IApplication;
import org.eclipse.equinox.app.IApplicationContext;

/**
* An application that runs the demo.<br>
* Run a client that shows the merging feature of the EMFstore
* Please note: this is the programmatic way of merging
* EMFStore also provides a default UI for merging
* If there is a problem with the connection to the server
* e.g. a network, a specific ESException will be thrown
*/
public class RaceConditionTestApplication implements IApplication {

/**
* {@inheritDoc}
*/
public Object start(IApplicationContext context) {

try {
cleanEmfstoreFolders();

// Create a client representation for a local server and start a
local server.
ESServer localServer = ESServer.FACTORY.createAndStartLocalServer();

/*
* Reuse the client from the hello world example. It will clean up
all local and remote projects and create
* one project with some content on the server and two checked-out
copies of the project on the client.
*/

org.eclipse.emf.emfstore.example.helloworld.Application.runClient(localServer);

// We run our own client code to demonstrate merging now.
runClient(localServer);

} catch (ESServerStartFailedException e) {
System.out.println("Server start failed!");
e.printStackTrace();
} catch (ESException e) {
/*
* If there is a problem with the connection to the server
* e.g. a network, a specific EMFStoreException will be thrown
*/
System.out.println("Connection to Server failed!");
e.printStackTrace();
}
return IApplication.EXIT_OK;
}

@SuppressWarnings("restriction")
private static void cleanEmfstoreFolders() {
try {
String clientWorkspaceDirectoryString =
Configuration.getFileInfo().getWorkspaceDirectory();
File clientWorkspaceDirectory = new File(clientWorkspaceDirectoryString);
System.out.println("Deleting client workspace directory " +
clientWorkspaceDirectory.getAbsolutePath());
FileUtil.deleteDirectory(clientWorkspaceDirectory, true);

String serverWorkspaceDirectoryString =
ServerConfiguration.getLocationProvider().getWorkspaceDirectory();
File serverWorkspaceDirectory = new File(serverWorkspaceDirectoryString);
System.out.println("Deleting server workspace directory " +
serverWorkspaceDirectory.getAbsolutePath());
FileUtil.deleteDirectory(serverWorkspaceDirectory, true);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

public static void runClient(ESServer server) throws ESException {
System.out.println("Client starting...");

ESWorkspace workspace = ESWorkspaceProvider.INSTANCE.getWorkspace();
final ESLocalProject demoProject = workspace.getLocalProjects().get(0);
League league = (League) demoProject.getModelElements().get(0);
final ESLocalProject demoProjectCopy =
workspace.getLocalProjects().get(1);
League leagueCopy = (League) demoProjectCopy.getModelElements().get(0);

// Change the name of the league in project 1,add a new player and
commit the change
league.setName("Euro-League");
Player newPlayer = BowlingFactory.eINSTANCE.createPlayer();
newPlayer.setName("Eugene");
league.getPlayers().add(newPlayer);

demoProject.commit(new ESSystemOutProgressMonitor());

/*
* Changing the name again value without calling update() on the copy
first will cause a conflict on commit.
* We also add one change which is non-conflicting, setting the name
of the first player.
*/
leagueCopy.setName("EU-League");
leagueCopy.getPlayers().get(0).setName("Johannes");

try {
demoProjectCopy.commit(new ESSystemOutProgressMonitor());
} catch (ESUpdateRequiredException e) {
// The commit failed since the other demoProject was committed first
and therefore demoProjectCopy needs an
// update
System.out.println("\nCommit of demoProjectCopy failed.");

// We run update in demoProjectCopy with an UpdateCallback to handle
conflicts
System.out.println("\nUpdate of demoProjectCopy with conflict
resolver...");
demoProjectCopy.update(ESVersionSpec.FACTORY.createHEAD(), new
ESUpdateCallback() {

boolean changeMade = false;

public void noChangesOnServer() {
/*
* do nothing if there are no changes on the server (in this
example we know
* there are changes anyway)
*/
}

public boolean inspectChanges(ESLocalProject project,
List<ESChangePackage> changes,
ESModelElementIdToEObjectMapping idToEObjectMapping) {

/*
* If another client commits to the project during
inspectChanges(), the UpdateController throws
* "ChangeConflictException: Conflict detected on update"
*/
if (false) {
changeAndCommit(demoProject);
}
// allow update to proceed, here we could also add some UI
return true;
}

public boolean conflictOccurred(ESConflictSet changeConflictSet,
IProgressMonitor monitor) {
/*
* If another client commits to the project during
inspectChanges(), the UpdateController throws
* "ChangeConflictException: Conflict detected on update"
*/
if (!changeMade && false) {
changeAndCommit(demoProject);
changeMade = true;
}
/*
* One or more conflicts have occured, they are delivered in a
change conflict set
* We know there is only one conflict so we grab it
*/
ESConflict conflict =
changeConflictSet.getConflicts().iterator().next();

/*
* We resolve the conflict by accepting all of the conflicting
local operations and rejecting all of
* the remote operations. This means that we revert the league name
change of demoProject and accept
* the league name change of demoProjectCopy. The player name
change in demoProject is accepted also
* since it was not conflicting with any other change.
*/
conflict.resolveConflict(conflict.getLocalOperations(),
conflict.getRemoteOperations());
/*
* Finally we claim to have resolved all conflicts so update will
try to proceed.
*/
return true;
}
}, new ESSystemOutProgressMonitor());

/*
* Commits from other clients at this point results in another
* "ESUpdateRequiredException: BaseVersion outdated, please update
before commit." exception being thrown.
*/
changeAndCommit(demoProject);

// commit merge result in project 2
System.out.println("\nCommit of merge result of demoProjectCopy");
demoProjectCopy.commit(new ESSystemOutProgressMonitor());

// After having merged the two projects update local project 1
System.out.println("\nUpdate of demoProject");
demoProject.update(new NullProgressMonitor());

// Finally we print the league and player names of both projects
System.out.println("\nLeague name in demoProject is now: " +
league.getName());
System.out.println("\nLeague name in demoProjectCopy is now: " +
leagueCopy.getName());
System.out.println("\nPlayer name in demoProject is now: " +
league.getPlayers().get(0).getName());
System.out.println("\nPlayer name in demoProjectCopy is now: " +
leagueCopy.getPlayers().get(0).getName());

}
}

private static void changeAndCommit(ESLocalProject demoProject) {
League league = (League) demoProject.getModelElements().get(0);
league.setName("Another name");
Player newPlayer = BowlingFactory.eINSTANCE.createPlayer();
newPlayer.setName("Bill");
league.getPlayers().add(newPlayer);
try {
demoProject.commit(new ESSystemOutProgressMonitor());
} catch (ESException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

public void stop() {
// do nothing
}
}
Re: [EMFStore] Updates and commits with concurrent users [message #1291816 is a reply to message #1290931] Fri, 11 April 2014 08:36 Go to previous messageGo to next message
Maximilian Koegel is currently offline Maximilian KoegelFriend
Messages: 253
Registered: July 2009
Senior Member
Hi Scott,

if I understand you correctly the "race conditions" are conceptional
race conditions not real technical race conditions. They result from the
conceptional problem that clients can commit at any time but only if
they have updated to the current head version they succeed with their
commit. This is since otherwise a merge is required because of
concurrent changes. If any client commits in between the other clients
update and commit attempt, the other client is forced to update again.
The exceptions that you encounter are probably
ESUpdateRequiredException, right?

Cheers,
Maximilian


Am 10.04.2014 17:11, schrieb scott@xxxxxxxx:
>
> My application needs a two-way sync capability in a multi-user (~10
> users) environment sharing a common project, but I'm not sure the best
> way to implement this. So far I naively implemented 'sync' as a
> combination of EMFStore's update() and commit() operations, and I'm
> struggling to avoid a race condition I've encountered during testing.
>
> Since update() and commit() are separate operations, there is room for
> another client (Client 2) to commit() in between Client 1's update() and
> commit() operations. update() and commit() are separate but sometimes
> dependent on one another and commits to the server can only be applied
> once the client and server have a common version of the model -- which
> may require an update from the server. Depending on the ordering of
> these interweaved update() and commit() calls to the server from
> concurrent clients, I've found that exceptions can be thrown that are
> hard to recover from as there is no limit to the number times the
> exceptions could recur.
>
> Here's the scenario I've encountered using EMFStore's example merge
> application to demonstrate how these "race conditions" occur. Since any
> of the users can execute a commit() at any point, I've inserted a method
> call at various points to simulate the effects of other clients
> committing to the shared project
>
> changeAndCommit(ESLocalProject demoProject)
>
> to commit changes to the shared project on the server in between the
> update, commit and commit retry for the demoProjectCopy.
>
> Is there a coding pattern or example code I should adopt to implement a
> two-way sync capability? Is there any locking feature in EMFStore that I
> could use to eliminate the potential for the interweaving of update()
> and commit() calls to the server from concurrent clients? Or perhaps
> some other way to simulate an atomic sync function?
>
> Here's the modified merge example code demonstrating the race condition:
>
> package org.eclipse.emf.emfstore.example.merging;
>
> import java.io.File;
> import java.io.IOException;
> import java.util.List;
>
> import org.eclipse.core.runtime.IProgressMonitor;
> import org.eclipse.core.runtime.NullProgressMonitor;
> import org.eclipse.emf.emfstore.bowling.BowlingFactory;
> import org.eclipse.emf.emfstore.bowling.League;
> import org.eclipse.emf.emfstore.bowling.Player;
> import org.eclipse.emf.emfstore.client.ESLocalProject;
> import org.eclipse.emf.emfstore.client.ESServer;
> import org.eclipse.emf.emfstore.client.ESWorkspace;
> import org.eclipse.emf.emfstore.client.ESWorkspaceProvider;
> import org.eclipse.emf.emfstore.client.callbacks.ESUpdateCallback;
> import
> org.eclipse.emf.emfstore.client.exceptions.ESServerStartFailedException;
> import org.eclipse.emf.emfstore.common.ESSystemOutProgressMonitor;
> import
> org.eclipse.emf.emfstore.common.model.ESModelElementIdToEObjectMapping;
> import org.eclipse.emf.emfstore.internal.client.model.Configuration;
> import
> org.eclipse.emf.emfstore.internal.client.model.exceptions.ChangeConflictException;
>
> import org.eclipse.emf.emfstore.internal.common.model.util.FileUtil;
> import org.eclipse.emf.emfstore.internal.server.ServerConfiguration;
> import org.eclipse.emf.emfstore.server.ESConflict;
> import org.eclipse.emf.emfstore.server.ESConflictSet;
> import org.eclipse.emf.emfstore.server.exceptions.ESException;
> import
> org.eclipse.emf.emfstore.server.exceptions.ESUpdateRequiredException;
> import org.eclipse.emf.emfstore.server.model.ESChangePackage;
> import org.eclipse.emf.emfstore.server.model.versionspec.ESVersionSpec;
> import org.eclipse.equinox.app.IApplication;
> import org.eclipse.equinox.app.IApplicationContext;
>
> /**
> * An application that runs the demo.<br>
> * Run a client that shows the merging feature of the EMFstore
> * Please note: this is the programmatic way of merging
> * EMFStore also provides a default UI for merging
> * If there is a problem with the connection to the server
> * e.g. a network, a specific ESException will be thrown
> */
> public class RaceConditionTestApplication implements IApplication {
>
> /**
> * {@inheritDoc}
> */
> public Object start(IApplicationContext context) {
>
> try {
> cleanEmfstoreFolders();
>
> // Create a client representation for a local server and
> start a local server.
> ESServer localServer =
> ESServer.FACTORY.createAndStartLocalServer();
>
> /*
> * Reuse the client from the hello world example. It will
> clean up all local and remote projects and create
> * one project with some content on the server and two
> checked-out copies of the project on the client.
> */
>
> org.eclipse.emf.emfstore.example.helloworld.Application.runClient(localServer);
>
>
> // We run our own client code to demonstrate merging now.
> runClient(localServer);
>
> } catch (ESServerStartFailedException e) {
> System.out.println("Server start failed!");
> e.printStackTrace();
> } catch (ESException e) {
> /*
> * If there is a problem with the connection to the server
> * e.g. a network, a specific EMFStoreException will be thrown
> */
> System.out.println("Connection to Server failed!");
> e.printStackTrace();
> }
> return IApplication.EXIT_OK;
> }
>
> @SuppressWarnings("restriction")
> private static void cleanEmfstoreFolders() {
> try {
> String clientWorkspaceDirectoryString =
> Configuration.getFileInfo().getWorkspaceDirectory();
> File clientWorkspaceDirectory = new
> File(clientWorkspaceDirectoryString);
> System.out.println("Deleting client workspace directory " +
> clientWorkspaceDirectory.getAbsolutePath());
> FileUtil.deleteDirectory(clientWorkspaceDirectory, true);
>
> String serverWorkspaceDirectoryString =
> ServerConfiguration.getLocationProvider().getWorkspaceDirectory();
> File serverWorkspaceDirectory = new
> File(serverWorkspaceDirectoryString);
> System.out.println("Deleting server workspace directory " +
> serverWorkspaceDirectory.getAbsolutePath());
> FileUtil.deleteDirectory(serverWorkspaceDirectory, true);
> } catch (IOException e) {
> throw new RuntimeException(e);
> }
> }
>
> public static void runClient(ESServer server) throws ESException {
> System.out.println("Client starting...");
>
> ESWorkspace workspace =
> ESWorkspaceProvider.INSTANCE.getWorkspace();
> final ESLocalProject demoProject =
> workspace.getLocalProjects().get(0);
> League league = (League) demoProject.getModelElements().get(0);
> final ESLocalProject demoProjectCopy =
> workspace.getLocalProjects().get(1);
> League leagueCopy = (League)
> demoProjectCopy.getModelElements().get(0);
>
> // Change the name of the league in project 1,add a new player
> and commit the change
> league.setName("Euro-League");
> Player newPlayer = BowlingFactory.eINSTANCE.createPlayer();
> newPlayer.setName("Eugene");
> league.getPlayers().add(newPlayer);
>
> demoProject.commit(new ESSystemOutProgressMonitor());
>
> /*
> * Changing the name again value without calling update() on the
> copy first will cause a conflict on commit.
> * We also add one change which is non-conflicting, setting the
> name of the first player.
> */
> leagueCopy.setName("EU-League");
> leagueCopy.getPlayers().get(0).setName("Johannes");
>
> try {
> demoProjectCopy.commit(new ESSystemOutProgressMonitor());
> } catch (ESUpdateRequiredException e) {
> // The commit failed since the other demoProject was
> committed first and therefore demoProjectCopy needs an
> // update
> System.out.println("\nCommit of demoProjectCopy failed.");
>
> // We run update in demoProjectCopy with an UpdateCallback
> to handle conflicts
> System.out.println("\nUpdate of demoProjectCopy with
> conflict resolver...");
> demoProjectCopy.update(ESVersionSpec.FACTORY.createHEAD(),
> new ESUpdateCallback() {
>
> boolean changeMade = false;
>
> public void noChangesOnServer() {
> /*
> * do nothing if there are no changes on the server
> (in this example we know
> * there are changes anyway)
> */
> }
>
> public boolean inspectChanges(ESLocalProject project,
> List<ESChangePackage> changes,
> ESModelElementIdToEObjectMapping idToEObjectMapping) {
>
> /*
> * If another client commits to the project during
> inspectChanges(), the UpdateController throws
> * "ChangeConflictException: Conflict detected on
> update"
> */
> if (false) {
> changeAndCommit(demoProject);
> }
> // allow update to proceed, here we could also add
> some UI
> return true;
> }
>
> public boolean conflictOccurred(ESConflictSet
> changeConflictSet, IProgressMonitor monitor) {
> /*
> * If another client commits to the project during
> inspectChanges(), the UpdateController throws
> * "ChangeConflictException: Conflict detected on
> update"
> */
> if (!changeMade && false) {
> changeAndCommit(demoProject);
> changeMade = true;
> }
> /*
> * One or more conflicts have occured, they are
> delivered in a change conflict set
> * We know there is only one conflict so we grab it
> */
> ESConflict conflict =
> changeConflictSet.getConflicts().iterator().next();
>
> /*
> * We resolve the conflict by accepting all of the
> conflicting local operations and rejecting all of
> * the remote operations. This means that we revert
> the league name change of demoProject and accept
> * the league name change of demoProjectCopy. The
> player name change in demoProject is accepted also
> * since it was not conflicting with any other change.
> */
>
> conflict.resolveConflict(conflict.getLocalOperations(),
> conflict.getRemoteOperations());
> /*
> * Finally we claim to have resolved all conflicts
> so update will try to proceed.
> */
> return true;
> }
> }, new ESSystemOutProgressMonitor());
>
> /*
> * Commits from other clients at this point results in another
> * "ESUpdateRequiredException: BaseVersion outdated, please
> update before commit." exception being thrown.
> */
> changeAndCommit(demoProject);
>
> // commit merge result in project 2
> System.out.println("\nCommit of merge result of
> demoProjectCopy");
> demoProjectCopy.commit(new ESSystemOutProgressMonitor());
>
> // After having merged the two projects update local project 1
> System.out.println("\nUpdate of demoProject");
> demoProject.update(new NullProgressMonitor());
>
> // Finally we print the league and player names of both
> projects
> System.out.println("\nLeague name in demoProject is now: "
> + league.getName());
> System.out.println("\nLeague name in demoProjectCopy is
> now: " + leagueCopy.getName());
> System.out.println("\nPlayer name in demoProject is now: " +
> league.getPlayers().get(0).getName());
> System.out.println("\nPlayer name in demoProjectCopy is now:
> " + leagueCopy.getPlayers().get(0).getName());
>
> }
> }
>
> private static void changeAndCommit(ESLocalProject demoProject) {
> League league = (League) demoProject.getModelElements().get(0);
> league.setName("Another name");
> Player newPlayer = BowlingFactory.eINSTANCE.createPlayer();
> newPlayer.setName("Bill");
> league.getPlayers().add(newPlayer);
> try {
> demoProject.commit(new ESSystemOutProgressMonitor());
> } catch (ESException e) {
> // TODO Auto-generated catch block
> e.printStackTrace();
> }
> }
>
> public void stop() {
> // do nothing
> }
> }


--
Maximilian Kögel

Get Professional Eclipse Support: http://eclipsesource.com/munich
Re: [EMFStore] Updates and commits with concurrent users [message #1294971 is a reply to message #1291816] Sun, 13 April 2014 23:56 Go to previous messageGo to next message
Scott Dybiec is currently offline Scott DybiecFriend
Messages: 148
Registered: July 2009
Senior Member
Yes, your explanation sounds right and I get both
ESUpdateRequiredException and ChangeConflictException depending where
the other client update occurs.

For example in the program I attached, if another client commits to the
project during inspectChanges(), the UpdateController throws
"ChangeConflictException: Conflict detected on
update".

Know any solutions to this problem?

$cott


On 4/11/2014 4:36 AM, Maximilian Koegel wrote:
> Hi Scott,
>
> if I understand you correctly the "race conditions" are conceptional
> race conditions not real technical race conditions. They result from the
> conceptional problem that clients can commit at any time but only if
> they have updated to the current head version they succeed with their
> commit. This is since otherwise a merge is required because of
> concurrent changes. If any client commits in between the other clients
> update and commit attempt, the other client is forced to update again.
> The exceptions that you encounter are probably
> ESUpdateRequiredException, right?
>
> Cheers,
> Maximilian
>
>
> Am 10.04.2014 17:11, schrieb scott@xxxxxxxx:
>>
>> My application needs a two-way sync capability in a multi-user (~10
>> users) environment sharing a common project, but I'm not sure the best
>> way to implement this. So far I naively implemented 'sync' as a
>> combination of EMFStore's update() and commit() operations, and I'm
>> struggling to avoid a race condition I've encountered during testing.
>>
>> Since update() and commit() are separate operations, there is room for
>> another client (Client 2) to commit() in between Client 1's update() and
>> commit() operations. update() and commit() are separate but sometimes
>> dependent on one another and commits to the server can only be applied
>> once the client and server have a common version of the model -- which
>> may require an update from the server. Depending on the ordering of
>> these interweaved update() and commit() calls to the server from
>> concurrent clients, I've found that exceptions can be thrown that are
>> hard to recover from as there is no limit to the number times the
>> exceptions could recur.
>>
>> Here's the scenario I've encountered using EMFStore's example merge
>> application to demonstrate how these "race conditions" occur. Since any
>> of the users can execute a commit() at any point, I've inserted a method
>> call at various points to simulate the effects of other clients
>> committing to the shared project
>>
>> changeAndCommit(ESLocalProject demoProject)
>>
>> to commit changes to the shared project on the server in between the
>> update, commit and commit retry for the demoProjectCopy.
>>
>> Is there a coding pattern or example code I should adopt to implement a
>> two-way sync capability? Is there any locking feature in EMFStore that I
>> could use to eliminate the potential for the interweaving of update()
>> and commit() calls to the server from concurrent clients? Or perhaps
>> some other way to simulate an atomic sync function?
>>
>> Here's the modified merge example code demonstrating the race condition:
>>
>> package org.eclipse.emf.emfstore.example.merging;
>>
>> import java.io.File;
>> import java.io.IOException;
>> import java.util.List;
>>
>> import org.eclipse.core.runtime.IProgressMonitor;
>> import org.eclipse.core.runtime.NullProgressMonitor;
>> import org.eclipse.emf.emfstore.bowling.BowlingFactory;
>> import org.eclipse.emf.emfstore.bowling.League;
>> import org.eclipse.emf.emfstore.bowling.Player;
>> import org.eclipse.emf.emfstore.client.ESLocalProject;
>> import org.eclipse.emf.emfstore.client.ESServer;
>> import org.eclipse.emf.emfstore.client.ESWorkspace;
>> import org.eclipse.emf.emfstore.client.ESWorkspaceProvider;
>> import org.eclipse.emf.emfstore.client.callbacks.ESUpdateCallback;
>> import
>> org.eclipse.emf.emfstore.client.exceptions.ESServerStartFailedException;
>> import org.eclipse.emf.emfstore.common.ESSystemOutProgressMonitor;
>> import
>> org.eclipse.emf.emfstore.common.model.ESModelElementIdToEObjectMapping;
>> import org.eclipse.emf.emfstore.internal.client.model.Configuration;
>> import
>> org.eclipse.emf.emfstore.internal.client.model.exceptions.ChangeConflictException;
>>
>> import org.eclipse.emf.emfstore.internal.common.model.util.FileUtil;
>> import org.eclipse.emf.emfstore.internal.server.ServerConfiguration;
>> import org.eclipse.emf.emfstore.server.ESConflict;
>> import org.eclipse.emf.emfstore.server.ESConflictSet;
>> import org.eclipse.emf.emfstore.server.exceptions.ESException;
>> import
>> org.eclipse.emf.emfstore.server.exceptions.ESUpdateRequiredException;
>> import org.eclipse.emf.emfstore.server.model.ESChangePackage;
>> import org.eclipse.emf.emfstore.server.model.versionspec.ESVersionSpec;
>> import org.eclipse.equinox.app.IApplication;
>> import org.eclipse.equinox.app.IApplicationContext;
>>
>> /**
>> * An application that runs the demo.<br>
>> * Run a client that shows the merging feature of the EMFstore
>> * Please note: this is the programmatic way of merging
>> * EMFStore also provides a default UI for merging
>> * If there is a problem with the connection to the server
>> * e.g. a network, a specific ESException will be thrown
>> */
>> public class RaceConditionTestApplication implements IApplication {
>>
>> /**
>> * {@inheritDoc}
>> */
>> public Object start(IApplicationContext context) {
>>
>> try {
>> cleanEmfstoreFolders();
>>
>> // Create a client representation for a local server and
>> start a local server.
>> ESServer localServer =
>> ESServer.FACTORY.createAndStartLocalServer();
>>
>> /*
>> * Reuse the client from the hello world example. It will
>> clean up all local and remote projects and create
>> * one project with some content on the server and two
>> checked-out copies of the project on the client.
>> */
>>
>> org.eclipse.emf.emfstore.example.helloworld.Application.runClient(localServer);
>>
>>
>> // We run our own client code to demonstrate merging now.
>> runClient(localServer);
>>
>> } catch (ESServerStartFailedException e) {
>> System.out.println("Server start failed!");
>> e.printStackTrace();
>> } catch (ESException e) {
>> /*
>> * If there is a problem with the connection to the server
>> * e.g. a network, a specific EMFStoreException will be thrown
>> */
>> System.out.println("Connection to Server failed!");
>> e.printStackTrace();
>> }
>> return IApplication.EXIT_OK;
>> }
>>
>> @SuppressWarnings("restriction")
>> private static void cleanEmfstoreFolders() {
>> try {
>> String clientWorkspaceDirectoryString =
>> Configuration.getFileInfo().getWorkspaceDirectory();
>> File clientWorkspaceDirectory = new
>> File(clientWorkspaceDirectoryString);
>> System.out.println("Deleting client workspace directory " +
>> clientWorkspaceDirectory.getAbsolutePath());
>> FileUtil.deleteDirectory(clientWorkspaceDirectory, true);
>>
>> String serverWorkspaceDirectoryString =
>> ServerConfiguration.getLocationProvider().getWorkspaceDirectory();
>> File serverWorkspaceDirectory = new
>> File(serverWorkspaceDirectoryString);
>> System.out.println("Deleting server workspace directory " +
>> serverWorkspaceDirectory.getAbsolutePath());
>> FileUtil.deleteDirectory(serverWorkspaceDirectory, true);
>> } catch (IOException e) {
>> throw new RuntimeException(e);
>> }
>> }
>>
>> public static void runClient(ESServer server) throws ESException {
>> System.out.println("Client starting...");
>>
>> ESWorkspace workspace =
>> ESWorkspaceProvider.INSTANCE.getWorkspace();
>> final ESLocalProject demoProject =
>> workspace.getLocalProjects().get(0);
>> League league = (League) demoProject.getModelElements().get(0);
>> final ESLocalProject demoProjectCopy =
>> workspace.getLocalProjects().get(1);
>> League leagueCopy = (League)
>> demoProjectCopy.getModelElements().get(0);
>>
>> // Change the name of the league in project 1,add a new player
>> and commit the change
>> league.setName("Euro-League");
>> Player newPlayer = BowlingFactory.eINSTANCE.createPlayer();
>> newPlayer.setName("Eugene");
>> league.getPlayers().add(newPlayer);
>>
>> demoProject.commit(new ESSystemOutProgressMonitor());
>>
>> /*
>> * Changing the name again value without calling update() on the
>> copy first will cause a conflict on commit.
>> * We also add one change which is non-conflicting, setting the
>> name of the first player.
>> */
>> leagueCopy.setName("EU-League");
>> leagueCopy.getPlayers().get(0).setName("Johannes");
>>
>> try {
>> demoProjectCopy.commit(new ESSystemOutProgressMonitor());
>> } catch (ESUpdateRequiredException e) {
>> // The commit failed since the other demoProject was
>> committed first and therefore demoProjectCopy needs an
>> // update
>> System.out.println("\nCommit of demoProjectCopy failed.");
>>
>> // We run update in demoProjectCopy with an UpdateCallback
>> to handle conflicts
>> System.out.println("\nUpdate of demoProjectCopy with
>> conflict resolver...");
>> demoProjectCopy.update(ESVersionSpec.FACTORY.createHEAD(),
>> new ESUpdateCallback() {
>>
>> boolean changeMade = false;
>>
>> public void noChangesOnServer() {
>> /*
>> * do nothing if there are no changes on the server
>> (in this example we know
>> * there are changes anyway)
>> */
>> }
>>
>> public boolean inspectChanges(ESLocalProject project,
>> List<ESChangePackage> changes,
>> ESModelElementIdToEObjectMapping idToEObjectMapping) {
>>
>> /*
>> * If another client commits to the project during
>> inspectChanges(), the UpdateController throws
>> * "ChangeConflictException: Conflict detected on
>> update"
>> */
>> if (false) {
>> changeAndCommit(demoProject);
>> }
>> // allow update to proceed, here we could also add
>> some UI
>> return true;
>> }
>>
>> public boolean conflictOccurred(ESConflictSet
>> changeConflictSet, IProgressMonitor monitor) {
>> /*
>> * If another client commits to the project during
>> inspectChanges(), the UpdateController throws
>> * "ChangeConflictException: Conflict detected on
>> update"
>> */
>> if (!changeMade && false) {
>> changeAndCommit(demoProject);
>> changeMade = true;
>> }
>> /*
>> * One or more conflicts have occured, they are
>> delivered in a change conflict set
>> * We know there is only one conflict so we grab it
>> */
>> ESConflict conflict =
>> changeConflictSet.getConflicts().iterator().next();
>>
>> /*
>> * We resolve the conflict by accepting all of the
>> conflicting local operations and rejecting all of
>> * the remote operations. This means that we revert
>> the league name change of demoProject and accept
>> * the league name change of demoProjectCopy. The
>> player name change in demoProject is accepted also
>> * since it was not conflicting with any other change.
>> */
>>
>> conflict.resolveConflict(conflict.getLocalOperations(),
>> conflict.getRemoteOperations());
>> /*
>> * Finally we claim to have resolved all conflicts
>> so update will try to proceed.
>> */
>> return true;
>> }
>> }, new ESSystemOutProgressMonitor());
>>
>> /*
>> * Commits from other clients at this point results in another
>> * "ESUpdateRequiredException: BaseVersion outdated, please
>> update before commit." exception being thrown.
>> */
>> changeAndCommit(demoProject);
>>
>> // commit merge result in project 2
>> System.out.println("\nCommit of merge result of
>> demoProjectCopy");
>> demoProjectCopy.commit(new ESSystemOutProgressMonitor());
>>
>> // After having merged the two projects update local project 1
>> System.out.println("\nUpdate of demoProject");
>> demoProject.update(new NullProgressMonitor());
>>
>> // Finally we print the league and player names of both
>> projects
>> System.out.println("\nLeague name in demoProject is now: "
>> + league.getName());
>> System.out.println("\nLeague name in demoProjectCopy is
>> now: " + leagueCopy.getName());
>> System.out.println("\nPlayer name in demoProject is now: " +
>> league.getPlayers().get(0).getName());
>> System.out.println("\nPlayer name in demoProjectCopy is now:
>> " + leagueCopy.getPlayers().get(0).getName());
>>
>> }
>> }
>>
>> private static void changeAndCommit(ESLocalProject demoProject) {
>> League league = (League) demoProject.getModelElements().get(0);
>> league.setName("Another name");
>> Player newPlayer = BowlingFactory.eINSTANCE.createPlayer();
>> newPlayer.setName("Bill");
>> league.getPlayers().add(newPlayer);
>> try {
>> demoProject.commit(new ESSystemOutProgressMonitor());
>> } catch (ESException e) {
>> // TODO Auto-generated catch block
>> e.printStackTrace();
>> }
>> }
>>
>> public void stop() {
>> // do nothing
>> }
>> }
>
>
Re: [EMFStore] Updates and commits with concurrent users [message #1295559 is a reply to message #1294971] Mon, 14 April 2014 11:24 Go to previous messageGo to next message
Maximilian Koegel is currently offline Maximilian KoegelFriend
Messages: 253
Registered: July 2009
Senior Member
ESUpdateRequiredException means your client´s project is outdated and
needs an Update. You could keep updating but there is no guarantee it
will ever succeed to commit since other clients could keep pushing in
commits. To block other clients from making commits would require
additional API for locking, which is currently not implemented.
ChangeConflictException means your client´s project has conflicting
local changes with an update incoming from the server, which need to be
resolved by merging. They could be auto-merged (conflictOccurred method)
by just dropping one of the changes, if this is something that is
acceptable in your application.

Cheers,
Maximilian




Am 14.04.2014 01:56, schrieb scott@xxxxxxxx:
> Yes, your explanation sounds right and I get both
> ESUpdateRequiredException and ChangeConflictException depending where
> the other client update occurs.
>
> For example in the program I attached, if another client commits to the
> project during inspectChanges(), the UpdateController throws
> "ChangeConflictException: Conflict detected on
> update".
>
> Know any solutions to this problem?
>
> $cott
>
>
> On 4/11/2014 4:36 AM, Maximilian Koegel wrote:
>> Hi Scott,
>>
>> if I understand you correctly the "race conditions" are conceptional
>> race conditions not real technical race conditions. They result from the
>> conceptional problem that clients can commit at any time but only if
>> they have updated to the current head version they succeed with their
>> commit. This is since otherwise a merge is required because of
>> concurrent changes. If any client commits in between the other clients
>> update and commit attempt, the other client is forced to update again.
>> The exceptions that you encounter are probably
>> ESUpdateRequiredException, right?
>>
>> Cheers,
>> Maximilian
>>
>>
>> Am 10.04.2014 17:11, schrieb scott@xxxxxxxx:
>>>
>>> My application needs a two-way sync capability in a multi-user (~10
>>> users) environment sharing a common project, but I'm not sure the best
>>> way to implement this. So far I naively implemented 'sync' as a
>>> combination of EMFStore's update() and commit() operations, and I'm
>>> struggling to avoid a race condition I've encountered during testing.
>>>
>>> Since update() and commit() are separate operations, there is room for
>>> another client (Client 2) to commit() in between Client 1's update() and
>>> commit() operations. update() and commit() are separate but sometimes
>>> dependent on one another and commits to the server can only be applied
>>> once the client and server have a common version of the model -- which
>>> may require an update from the server. Depending on the ordering of
>>> these interweaved update() and commit() calls to the server from
>>> concurrent clients, I've found that exceptions can be thrown that are
>>> hard to recover from as there is no limit to the number times the
>>> exceptions could recur.
>>>
>>> Here's the scenario I've encountered using EMFStore's example merge
>>> application to demonstrate how these "race conditions" occur. Since any
>>> of the users can execute a commit() at any point, I've inserted a method
>>> call at various points to simulate the effects of other clients
>>> committing to the shared project
>>>
>>> changeAndCommit(ESLocalProject demoProject)
>>>
>>> to commit changes to the shared project on the server in between the
>>> update, commit and commit retry for the demoProjectCopy.
>>>
>>> Is there a coding pattern or example code I should adopt to implement a
>>> two-way sync capability? Is there any locking feature in EMFStore that I
>>> could use to eliminate the potential for the interweaving of update()
>>> and commit() calls to the server from concurrent clients? Or perhaps
>>> some other way to simulate an atomic sync function?
>>>
>>> Here's the modified merge example code demonstrating the race condition:
>>>
>>> package org.eclipse.emf.emfstore.example.merging;
>>>
>>> import java.io.File;
>>> import java.io.IOException;
>>> import java.util.List;
>>>
>>> import org.eclipse.core.runtime.IProgressMonitor;
>>> import org.eclipse.core.runtime.NullProgressMonitor;
>>> import org.eclipse.emf.emfstore.bowling.BowlingFactory;
>>> import org.eclipse.emf.emfstore.bowling.League;
>>> import org.eclipse.emf.emfstore.bowling.Player;
>>> import org.eclipse.emf.emfstore.client.ESLocalProject;
>>> import org.eclipse.emf.emfstore.client.ESServer;
>>> import org.eclipse.emf.emfstore.client.ESWorkspace;
>>> import org.eclipse.emf.emfstore.client.ESWorkspaceProvider;
>>> import org.eclipse.emf.emfstore.client.callbacks.ESUpdateCallback;
>>> import
>>> org.eclipse.emf.emfstore.client.exceptions.ESServerStartFailedException;
>>> import org.eclipse.emf.emfstore.common.ESSystemOutProgressMonitor;
>>> import
>>> org.eclipse.emf.emfstore.common.model.ESModelElementIdToEObjectMapping;
>>> import org.eclipse.emf.emfstore.internal.client.model.Configuration;
>>> import
>>> org.eclipse.emf.emfstore.internal.client.model.exceptions.ChangeConflictException;
>>>
>>>
>>> import org.eclipse.emf.emfstore.internal.common.model.util.FileUtil;
>>> import org.eclipse.emf.emfstore.internal.server.ServerConfiguration;
>>> import org.eclipse.emf.emfstore.server.ESConflict;
>>> import org.eclipse.emf.emfstore.server.ESConflictSet;
>>> import org.eclipse.emf.emfstore.server.exceptions.ESException;
>>> import
>>> org.eclipse.emf.emfstore.server.exceptions.ESUpdateRequiredException;
>>> import org.eclipse.emf.emfstore.server.model.ESChangePackage;
>>> import org.eclipse.emf.emfstore.server.model.versionspec.ESVersionSpec;
>>> import org.eclipse.equinox.app.IApplication;
>>> import org.eclipse.equinox.app.IApplicationContext;
>>>
>>> /**
>>> * An application that runs the demo.<br>
>>> * Run a client that shows the merging feature of the EMFstore
>>> * Please note: this is the programmatic way of merging
>>> * EMFStore also provides a default UI for merging
>>> * If there is a problem with the connection to the server
>>> * e.g. a network, a specific ESException will be thrown
>>> */
>>> public class RaceConditionTestApplication implements IApplication {
>>>
>>> /**
>>> * {@inheritDoc}
>>> */
>>> public Object start(IApplicationContext context) {
>>>
>>> try {
>>> cleanEmfstoreFolders();
>>>
>>> // Create a client representation for a local server and
>>> start a local server.
>>> ESServer localServer =
>>> ESServer.FACTORY.createAndStartLocalServer();
>>>
>>> /*
>>> * Reuse the client from the hello world example. It will
>>> clean up all local and remote projects and create
>>> * one project with some content on the server and two
>>> checked-out copies of the project on the client.
>>> */
>>>
>>> org.eclipse.emf.emfstore.example.helloworld.Application.runClient(localServer);
>>>
>>>
>>>
>>> // We run our own client code to demonstrate merging now.
>>> runClient(localServer);
>>>
>>> } catch (ESServerStartFailedException e) {
>>> System.out.println("Server start failed!");
>>> e.printStackTrace();
>>> } catch (ESException e) {
>>> /*
>>> * If there is a problem with the connection to the server
>>> * e.g. a network, a specific EMFStoreException will be
>>> thrown
>>> */
>>> System.out.println("Connection to Server failed!");
>>> e.printStackTrace();
>>> }
>>> return IApplication.EXIT_OK;
>>> }
>>>
>>> @SuppressWarnings("restriction")
>>> private static void cleanEmfstoreFolders() {
>>> try {
>>> String clientWorkspaceDirectoryString =
>>> Configuration.getFileInfo().getWorkspaceDirectory();
>>> File clientWorkspaceDirectory = new
>>> File(clientWorkspaceDirectoryString);
>>> System.out.println("Deleting client workspace directory " +
>>> clientWorkspaceDirectory.getAbsolutePath());
>>> FileUtil.deleteDirectory(clientWorkspaceDirectory, true);
>>>
>>> String serverWorkspaceDirectoryString =
>>> ServerConfiguration.getLocationProvider().getWorkspaceDirectory();
>>> File serverWorkspaceDirectory = new
>>> File(serverWorkspaceDirectoryString);
>>> System.out.println("Deleting server workspace directory " +
>>> serverWorkspaceDirectory.getAbsolutePath());
>>> FileUtil.deleteDirectory(serverWorkspaceDirectory, true);
>>> } catch (IOException e) {
>>> throw new RuntimeException(e);
>>> }
>>> }
>>>
>>> public static void runClient(ESServer server) throws ESException {
>>> System.out.println("Client starting...");
>>>
>>> ESWorkspace workspace =
>>> ESWorkspaceProvider.INSTANCE.getWorkspace();
>>> final ESLocalProject demoProject =
>>> workspace.getLocalProjects().get(0);
>>> League league = (League) demoProject.getModelElements().get(0);
>>> final ESLocalProject demoProjectCopy =
>>> workspace.getLocalProjects().get(1);
>>> League leagueCopy = (League)
>>> demoProjectCopy.getModelElements().get(0);
>>>
>>> // Change the name of the league in project 1,add a new player
>>> and commit the change
>>> league.setName("Euro-League");
>>> Player newPlayer = BowlingFactory.eINSTANCE.createPlayer();
>>> newPlayer.setName("Eugene");
>>> league.getPlayers().add(newPlayer);
>>>
>>> demoProject.commit(new ESSystemOutProgressMonitor());
>>>
>>> /*
>>> * Changing the name again value without calling update() on
>>> the
>>> copy first will cause a conflict on commit.
>>> * We also add one change which is non-conflicting, setting the
>>> name of the first player.
>>> */
>>> leagueCopy.setName("EU-League");
>>> leagueCopy.getPlayers().get(0).setName("Johannes");
>>>
>>> try {
>>> demoProjectCopy.commit(new ESSystemOutProgressMonitor());
>>> } catch (ESUpdateRequiredException e) {
>>> // The commit failed since the other demoProject was
>>> committed first and therefore demoProjectCopy needs an
>>> // update
>>> System.out.println("\nCommit of demoProjectCopy failed.");
>>>
>>> // We run update in demoProjectCopy with an UpdateCallback
>>> to handle conflicts
>>> System.out.println("\nUpdate of demoProjectCopy with
>>> conflict resolver...");
>>> demoProjectCopy.update(ESVersionSpec.FACTORY.createHEAD(),
>>> new ESUpdateCallback() {
>>>
>>> boolean changeMade = false;
>>>
>>> public void noChangesOnServer() {
>>> /*
>>> * do nothing if there are no changes on the server
>>> (in this example we know
>>> * there are changes anyway)
>>> */
>>> }
>>>
>>> public boolean inspectChanges(ESLocalProject project,
>>> List<ESChangePackage> changes,
>>> ESModelElementIdToEObjectMapping
>>> idToEObjectMapping) {
>>>
>>> /*
>>> * If another client commits to the project during
>>> inspectChanges(), the UpdateController throws
>>> * "ChangeConflictException: Conflict detected on
>>> update"
>>> */
>>> if (false) {
>>> changeAndCommit(demoProject);
>>> }
>>> // allow update to proceed, here we could also add
>>> some UI
>>> return true;
>>> }
>>>
>>> public boolean conflictOccurred(ESConflictSet
>>> changeConflictSet, IProgressMonitor monitor) {
>>> /*
>>> * If another client commits to the project during
>>> inspectChanges(), the UpdateController throws
>>> * "ChangeConflictException: Conflict detected on
>>> update"
>>> */
>>> if (!changeMade && false) {
>>> changeAndCommit(demoProject);
>>> changeMade = true;
>>> }
>>> /*
>>> * One or more conflicts have occured, they are
>>> delivered in a change conflict set
>>> * We know there is only one conflict so we grab it
>>> */
>>> ESConflict conflict =
>>> changeConflictSet.getConflicts().iterator().next();
>>>
>>> /*
>>> * We resolve the conflict by accepting all of the
>>> conflicting local operations and rejecting all of
>>> * the remote operations. This means that we revert
>>> the league name change of demoProject and accept
>>> * the league name change of demoProjectCopy. The
>>> player name change in demoProject is accepted also
>>> * since it was not conflicting with any other
>>> change.
>>> */
>>>
>>> conflict.resolveConflict(conflict.getLocalOperations(),
>>> conflict.getRemoteOperations());
>>> /*
>>> * Finally we claim to have resolved all conflicts
>>> so update will try to proceed.
>>> */
>>> return true;
>>> }
>>> }, new ESSystemOutProgressMonitor());
>>>
>>> /*
>>> * Commits from other clients at this point results in
>>> another
>>> * "ESUpdateRequiredException: BaseVersion outdated, please
>>> update before commit." exception being thrown.
>>> */
>>> changeAndCommit(demoProject);
>>>
>>> // commit merge result in project 2
>>> System.out.println("\nCommit of merge result of
>>> demoProjectCopy");
>>> demoProjectCopy.commit(new ESSystemOutProgressMonitor());
>>>
>>> // After having merged the two projects update local
>>> project 1
>>> System.out.println("\nUpdate of demoProject");
>>> demoProject.update(new NullProgressMonitor());
>>>
>>> // Finally we print the league and player names of both
>>> projects
>>> System.out.println("\nLeague name in demoProject is now: "
>>> + league.getName());
>>> System.out.println("\nLeague name in demoProjectCopy is
>>> now: " + leagueCopy.getName());
>>> System.out.println("\nPlayer name in demoProject is now:
>>> " +
>>> league.getPlayers().get(0).getName());
>>> System.out.println("\nPlayer name in demoProjectCopy is
>>> now:
>>> " + leagueCopy.getPlayers().get(0).getName());
>>>
>>> }
>>> }
>>>
>>> private static void changeAndCommit(ESLocalProject demoProject) {
>>> League league = (League) demoProject.getModelElements().get(0);
>>> league.setName("Another name");
>>> Player newPlayer = BowlingFactory.eINSTANCE.createPlayer();
>>> newPlayer.setName("Bill");
>>> league.getPlayers().add(newPlayer);
>>> try {
>>> demoProject.commit(new ESSystemOutProgressMonitor());
>>> } catch (ESException e) {
>>> // TODO Auto-generated catch block
>>> e.printStackTrace();
>>> }
>>> }
>>>
>>> public void stop() {
>>> // do nothing
>>> }
>>> }
>>
>>
>


--
Maximilian Kögel

Get Professional Eclipse Support: http://eclipsesource.com/munich
Re: [EMFStore] Updates and commits with concurrent users [message #1295655 is a reply to message #1295559] Mon, 14 April 2014 13:05 Go to previous messageGo to next message
Scott Dybiec is currently offline Scott DybiecFriend
Messages: 148
Registered: July 2009
Senior Member
OK, thanks for the quick response.

Without a locking mechanism or a native two-way sync() function, it
sounds like I'll need to repeatedly retry the update() and commit()
until it succeeds or exceeds a retry limit. Hopefully that will work for
the limited number of concurrent users I need to support.

Any plans to implement a native sync() or lock mechanism?

Thanks,

Scott


On 4/14/2014 7:24 AM, Maximilian Koegel wrote:
> ESUpdateRequiredException means your client´s project is outdated and
> needs an Update. You could keep updating but there is no guarantee it
> will ever succeed to commit since other clients could keep pushing in
> commits. To block other clients from making commits would require
> additional API for locking, which is currently not implemented.
> ChangeConflictException means your client´s project has conflicting
> local changes with an update incoming from the server, which need to be
> resolved by merging. They could be auto-merged (conflictOccurred method)
> by just dropping one of the changes, if this is something that is
> acceptable in your application.
>
> Cheers,
> Maximilian
>
>
>
>
> Am 14.04.2014 01:56, schrieb scott@xxxxxxxx:
>> Yes, your explanation sounds right and I get both
>> ESUpdateRequiredException and ChangeConflictException depending where
>> the other client update occurs.
>>
>> For example in the program I attached, if another client commits to the
>> project during inspectChanges(), the UpdateController throws
>> "ChangeConflictException: Conflict detected on
>> update".
>>
>> Know any solutions to this problem?
>>
>> $cott
>>
>>
>> On 4/11/2014 4:36 AM, Maximilian Koegel wrote:
>>> Hi Scott,
>>>
>>> if I understand you correctly the "race conditions" are conceptional
>>> race conditions not real technical race conditions. They result from the
>>> conceptional problem that clients can commit at any time but only if
>>> they have updated to the current head version they succeed with their
>>> commit. This is since otherwise a merge is required because of
>>> concurrent changes. If any client commits in between the other clients
>>> update and commit attempt, the other client is forced to update again.
>>> The exceptions that you encounter are probably
>>> ESUpdateRequiredException, right?
>>>
>>> Cheers,
>>> Maximilian
>>>
>>>
>>> Am 10.04.2014 17:11, schrieb scott@xxxxxxxx:
>>>>
>>>> My application needs a two-way sync capability in a multi-user (~10
>>>> users) environment sharing a common project, but I'm not sure the best
>>>> way to implement this. So far I naively implemented 'sync' as a
>>>> combination of EMFStore's update() and commit() operations, and I'm
>>>> struggling to avoid a race condition I've encountered during testing.
>>>>
>>>> Since update() and commit() are separate operations, there is room for
>>>> another client (Client 2) to commit() in between Client 1's update() and
>>>> commit() operations. update() and commit() are separate but sometimes
>>>> dependent on one another and commits to the server can only be applied
>>>> once the client and server have a common version of the model -- which
>>>> may require an update from the server. Depending on the ordering of
>>>> these interweaved update() and commit() calls to the server from
>>>> concurrent clients, I've found that exceptions can be thrown that are
>>>> hard to recover from as there is no limit to the number times the
>>>> exceptions could recur.
>>>>
>>>> Here's the scenario I've encountered using EMFStore's example merge
>>>> application to demonstrate how these "race conditions" occur. Since any
>>>> of the users can execute a commit() at any point, I've inserted a method
>>>> call at various points to simulate the effects of other clients
>>>> committing to the shared project
>>>>
>>>> changeAndCommit(ESLocalProject demoProject)
>>>>
>>>> to commit changes to the shared project on the server in between the
>>>> update, commit and commit retry for the demoProjectCopy.
>>>>
>>>> Is there a coding pattern or example code I should adopt to implement a
>>>> two-way sync capability? Is there any locking feature in EMFStore that I
>>>> could use to eliminate the potential for the interweaving of update()
>>>> and commit() calls to the server from concurrent clients? Or perhaps
>>>> some other way to simulate an atomic sync function?
>>>>
>>>> Here's the modified merge example code demonstrating the race condition:
>>>>
>>>> package org.eclipse.emf.emfstore.example.merging;
>>>>
>>>> import java.io.File;
>>>> import java.io.IOException;
>>>> import java.util.List;
>>>>
>>>> import org.eclipse.core.runtime.IProgressMonitor;
>>>> import org.eclipse.core.runtime.NullProgressMonitor;
>>>> import org.eclipse.emf.emfstore.bowling.BowlingFactory;
>>>> import org.eclipse.emf.emfstore.bowling.League;
>>>> import org.eclipse.emf.emfstore.bowling.Player;
>>>> import org.eclipse.emf.emfstore.client.ESLocalProject;
>>>> import org.eclipse.emf.emfstore.client.ESServer;
>>>> import org.eclipse.emf.emfstore.client.ESWorkspace;
>>>> import org.eclipse.emf.emfstore.client.ESWorkspaceProvider;
>>>> import org.eclipse.emf.emfstore.client.callbacks.ESUpdateCallback;
>>>> import
>>>> org.eclipse.emf.emfstore.client.exceptions.ESServerStartFailedException;
>>>> import org.eclipse.emf.emfstore.common.ESSystemOutProgressMonitor;
>>>> import
>>>> org.eclipse.emf.emfstore.common.model.ESModelElementIdToEObjectMapping;
>>>> import org.eclipse.emf.emfstore.internal.client.model.Configuration;
>>>> import
>>>> org.eclipse.emf.emfstore.internal.client.model.exceptions.ChangeConflictException;
>>>>
>>>>
>>>> import org.eclipse.emf.emfstore.internal.common.model.util.FileUtil;
>>>> import org.eclipse.emf.emfstore.internal.server.ServerConfiguration;
>>>> import org.eclipse.emf.emfstore.server.ESConflict;
>>>> import org.eclipse.emf.emfstore.server.ESConflictSet;
>>>> import org.eclipse.emf.emfstore.server.exceptions.ESException;
>>>> import
>>>> org.eclipse.emf.emfstore.server.exceptions.ESUpdateRequiredException;
>>>> import org.eclipse.emf.emfstore.server.model.ESChangePackage;
>>>> import org.eclipse.emf.emfstore.server.model.versionspec.ESVersionSpec;
>>>> import org.eclipse.equinox.app.IApplication;
>>>> import org.eclipse.equinox.app.IApplicationContext;
>>>>
>>>> /**
>>>> * An application that runs the demo.<br>
>>>> * Run a client that shows the merging feature of the EMFstore
>>>> * Please note: this is the programmatic way of merging
>>>> * EMFStore also provides a default UI for merging
>>>> * If there is a problem with the connection to the server
>>>> * e.g. a network, a specific ESException will be thrown
>>>> */
>>>> public class RaceConditionTestApplication implements IApplication {
>>>>
>>>> /**
>>>> * {@inheritDoc}
>>>> */
>>>> public Object start(IApplicationContext context) {
>>>>
>>>> try {
>>>> cleanEmfstoreFolders();
>>>>
>>>> // Create a client representation for a local server and
>>>> start a local server.
>>>> ESServer localServer =
>>>> ESServer.FACTORY.createAndStartLocalServer();
>>>>
>>>> /*
>>>> * Reuse the client from the hello world example. It will
>>>> clean up all local and remote projects and create
>>>> * one project with some content on the server and two
>>>> checked-out copies of the project on the client.
>>>> */
>>>>
>>>> org.eclipse.emf.emfstore.example.helloworld.Application.runClient(localServer);
>>>>
>>>>
>>>>
>>>> // We run our own client code to demonstrate merging now.
>>>> runClient(localServer);
>>>>
>>>> } catch (ESServerStartFailedException e) {
>>>> System.out.println("Server start failed!");
>>>> e.printStackTrace();
>>>> } catch (ESException e) {
>>>> /*
>>>> * If there is a problem with the connection to the server
>>>> * e.g. a network, a specific EMFStoreException will be
>>>> thrown
>>>> */
>>>> System.out.println("Connection to Server failed!");
>>>> e.printStackTrace();
>>>> }
>>>> return IApplication.EXIT_OK;
>>>> }
>>>>
>>>> @SuppressWarnings("restriction")
>>>> private static void cleanEmfstoreFolders() {
>>>> try {
>>>> String clientWorkspaceDirectoryString =
>>>> Configuration.getFileInfo().getWorkspaceDirectory();
>>>> File clientWorkspaceDirectory = new
>>>> File(clientWorkspaceDirectoryString);
>>>> System.out.println("Deleting client workspace directory " +
>>>> clientWorkspaceDirectory.getAbsolutePath());
>>>> FileUtil.deleteDirectory(clientWorkspaceDirectory, true);
>>>>
>>>> String serverWorkspaceDirectoryString =
>>>> ServerConfiguration.getLocationProvider().getWorkspaceDirectory();
>>>> File serverWorkspaceDirectory = new
>>>> File(serverWorkspaceDirectoryString);
>>>> System.out.println("Deleting server workspace directory " +
>>>> serverWorkspaceDirectory.getAbsolutePath());
>>>> FileUtil.deleteDirectory(serverWorkspaceDirectory, true);
>>>> } catch (IOException e) {
>>>> throw new RuntimeException(e);
>>>> }
>>>> }
>>>>
>>>> public static void runClient(ESServer server) throws ESException {
>>>> System.out.println("Client starting...");
>>>>
>>>> ESWorkspace workspace =
>>>> ESWorkspaceProvider.INSTANCE.getWorkspace();
>>>> final ESLocalProject demoProject =
>>>> workspace.getLocalProjects().get(0);
>>>> League league = (League) demoProject.getModelElements().get(0);
>>>> final ESLocalProject demoProjectCopy =
>>>> workspace.getLocalProjects().get(1);
>>>> League leagueCopy = (League)
>>>> demoProjectCopy.getModelElements().get(0);
>>>>
>>>> // Change the name of the league in project 1,add a new player
>>>> and commit the change
>>>> league.setName("Euro-League");
>>>> Player newPlayer = BowlingFactory.eINSTANCE.createPlayer();
>>>> newPlayer.setName("Eugene");
>>>> league.getPlayers().add(newPlayer);
>>>>
>>>> demoProject.commit(new ESSystemOutProgressMonitor());
>>>>
>>>> /*
>>>> * Changing the name again value without calling update() on
>>>> the
>>>> copy first will cause a conflict on commit.
>>>> * We also add one change which is non-conflicting, setting the
>>>> name of the first player.
>>>> */
>>>> leagueCopy.setName("EU-League");
>>>> leagueCopy.getPlayers().get(0).setName("Johannes");
>>>>
>>>> try {
>>>> demoProjectCopy.commit(new ESSystemOutProgressMonitor());
>>>> } catch (ESUpdateRequiredException e) {
>>>> // The commit failed since the other demoProject was
>>>> committed first and therefore demoProjectCopy needs an
>>>> // update
>>>> System.out.println("\nCommit of demoProjectCopy failed.");
>>>>
>>>> // We run update in demoProjectCopy with an UpdateCallback
>>>> to handle conflicts
>>>> System.out.println("\nUpdate of demoProjectCopy with
>>>> conflict resolver...");
>>>> demoProjectCopy.update(ESVersionSpec.FACTORY.createHEAD(),
>>>> new ESUpdateCallback() {
>>>>
>>>> boolean changeMade = false;
>>>>
>>>> public void noChangesOnServer() {
>>>> /*
>>>> * do nothing if there are no changes on the server
>>>> (in this example we know
>>>> * there are changes anyway)
>>>> */
>>>> }
>>>>
>>>> public boolean inspectChanges(ESLocalProject project,
>>>> List<ESChangePackage> changes,
>>>> ESModelElementIdToEObjectMapping
>>>> idToEObjectMapping) {
>>>>
>>>> /*
>>>> * If another client commits to the project during
>>>> inspectChanges(), the UpdateController throws
>>>> * "ChangeConflictException: Conflict detected on
>>>> update"
>>>> */
>>>> if (false) {
>>>> changeAndCommit(demoProject);
>>>> }
>>>> // allow update to proceed, here we could also add
>>>> some UI
>>>> return true;
>>>> }
>>>>
>>>> public boolean conflictOccurred(ESConflictSet
>>>> changeConflictSet, IProgressMonitor monitor) {
>>>> /*
>>>> * If another client commits to the project during
>>>> inspectChanges(), the UpdateController throws
>>>> * "ChangeConflictException: Conflict detected on
>>>> update"
>>>> */
>>>> if (!changeMade && false) {
>>>> changeAndCommit(demoProject);
>>>> changeMade = true;
>>>> }
>>>> /*
>>>> * One or more conflicts have occured, they are
>>>> delivered in a change conflict set
>>>> * We know there is only one conflict so we grab it
>>>> */
>>>> ESConflict conflict =
>>>> changeConflictSet.getConflicts().iterator().next();
>>>>
>>>> /*
>>>> * We resolve the conflict by accepting all of the
>>>> conflicting local operations and rejecting all of
>>>> * the remote operations. This means that we revert
>>>> the league name change of demoProject and accept
>>>> * the league name change of demoProjectCopy. The
>>>> player name change in demoProject is accepted also
>>>> * since it was not conflicting with any other
>>>> change.
>>>> */
>>>>
>>>> conflict.resolveConflict(conflict.getLocalOperations(),
>>>> conflict.getRemoteOperations());
>>>> /*
>>>> * Finally we claim to have resolved all conflicts
>>>> so update will try to proceed.
>>>> */
>>>> return true;
>>>> }
>>>> }, new ESSystemOutProgressMonitor());
>>>>
>>>> /*
>>>> * Commits from other clients at this point results in
>>>> another
>>>> * "ESUpdateRequiredException: BaseVersion outdated, please
>>>> update before commit." exception being thrown.
>>>> */
>>>> changeAndCommit(demoProject);
>>>>
>>>> // commit merge result in project 2
>>>> System.out.println("\nCommit of merge result of
>>>> demoProjectCopy");
>>>> demoProjectCopy.commit(new ESSystemOutProgressMonitor());
>>>>
>>>> // After having merged the two projects update local
>>>> project 1
>>>> System.out.println("\nUpdate of demoProject");
>>>> demoProject.update(new NullProgressMonitor());
>>>>
>>>> // Finally we print the league and player names of both
>>>> projects
>>>> System.out.println("\nLeague name in demoProject is now: "
>>>> + league.getName());
>>>> System.out.println("\nLeague name in demoProjectCopy is
>>>> now: " + leagueCopy.getName());
>>>> System.out.println("\nPlayer name in demoProject is now:
>>>> " +
>>>> league.getPlayers().get(0).getName());
>>>> System.out.println("\nPlayer name in demoProjectCopy is
>>>> now:
>>>> " + leagueCopy.getPlayers().get(0).getName());
>>>>
>>>> }
>>>> }
>>>>
>>>> private static void changeAndCommit(ESLocalProject demoProject) {
>>>> League league = (League) demoProject.getModelElements().get(0);
>>>> league.setName("Another name");
>>>> Player newPlayer = BowlingFactory.eINSTANCE.createPlayer();
>>>> newPlayer.setName("Bill");
>>>> league.getPlayers().add(newPlayer);
>>>> try {
>>>> demoProject.commit(new ESSystemOutProgressMonitor());
>>>> } catch (ESException e) {
>>>> // TODO Auto-generated catch block
>>>> e.printStackTrace();
>>>> }
>>>> }
>>>>
>>>> public void stop() {
>>>> // do nothing
>>>> }
>>>> }
>>>
>>>
>>
>
>
Re: [EMFStore] Updates and commits with concurrent users [message #1296930 is a reply to message #1295655] Tue, 15 April 2014 09:51 Go to previous messageGo to next message
Maximilian Koegel is currently offline Maximilian KoegelFriend
Messages: 253
Registered: July 2009
Senior Member
Yes, that is correct. EMFStore is intended for offline usage, like git
or svn, so typically you will not have many competing commits at the
same time. The problem with an atomic update and commit is that often
merging is done interactively so a lock would hold of all other clients.
I would accept a time-baed lock for a project a additional API though in
case you would propose a patch. We have no plans to implement something
like that ourself at his time.

Cheers,
Maximilian

Am 14.04.2014 15:05, schrieb scott@xxxxxxxx:
> OK, thanks for the quick response.
>
> Without a locking mechanism or a native two-way sync() function, it
> sounds like I'll need to repeatedly retry the update() and commit()
> until it succeeds or exceeds a retry limit. Hopefully that will work for
> the limited number of concurrent users I need to support.
>
> Any plans to implement a native sync() or lock mechanism?
>
> Thanks,
>
> Scott
>
>
> On 4/14/2014 7:24 AM, Maximilian Koegel wrote:
>> ESUpdateRequiredException means your client´s project is outdated and
>> needs an Update. You could keep updating but there is no guarantee it
>> will ever succeed to commit since other clients could keep pushing in
>> commits. To block other clients from making commits would require
>> additional API for locking, which is currently not implemented.
>> ChangeConflictException means your client´s project has conflicting
>> local changes with an update incoming from the server, which need to be
>> resolved by merging. They could be auto-merged (conflictOccurred method)
>> by just dropping one of the changes, if this is something that is
>> acceptable in your application.
>>
>> Cheers,
>> Maximilian
>>
>>
>>
>>
>> Am 14.04.2014 01:56, schrieb scott@xxxxxxxx:
>>> Yes, your explanation sounds right and I get both
>>> ESUpdateRequiredException and ChangeConflictException depending where
>>> the other client update occurs.
>>>
>>> For example in the program I attached, if another client commits to the
>>> project during inspectChanges(), the UpdateController throws
>>> "ChangeConflictException: Conflict detected on
>>> update".
>>>
>>> Know any solutions to this problem?
>>>
>>> $cott
>>>
>>>
>>> On 4/11/2014 4:36 AM, Maximilian Koegel wrote:
>>>> Hi Scott,
>>>>
>>>> if I understand you correctly the "race conditions" are conceptional
>>>> race conditions not real technical race conditions. They result from
>>>> the
>>>> conceptional problem that clients can commit at any time but only if
>>>> they have updated to the current head version they succeed with their
>>>> commit. This is since otherwise a merge is required because of
>>>> concurrent changes. If any client commits in between the other clients
>>>> update and commit attempt, the other client is forced to update again.
>>>> The exceptions that you encounter are probably
>>>> ESUpdateRequiredException, right?
>>>>
>>>> Cheers,
>>>> Maximilian
>>>>
>>>>
>>>> Am 10.04.2014 17:11, schrieb scott@xxxxxxxx:
>>>>>
>>>>> My application needs a two-way sync capability in a multi-user (~10
>>>>> users) environment sharing a common project, but I'm not sure the best
>>>>> way to implement this. So far I naively implemented 'sync' as a
>>>>> combination of EMFStore's update() and commit() operations, and I'm
>>>>> struggling to avoid a race condition I've encountered during testing.
>>>>>
>>>>> Since update() and commit() are separate operations, there is room for
>>>>> another client (Client 2) to commit() in between Client 1's
>>>>> update() and
>>>>> commit() operations. update() and commit() are separate but sometimes
>>>>> dependent on one another and commits to the server can only be applied
>>>>> once the client and server have a common version of the model -- which
>>>>> may require an update from the server. Depending on the ordering of
>>>>> these interweaved update() and commit() calls to the server from
>>>>> concurrent clients, I've found that exceptions can be thrown that are
>>>>> hard to recover from as there is no limit to the number times the
>>>>> exceptions could recur.
>>>>>
>>>>> Here's the scenario I've encountered using EMFStore's example merge
>>>>> application to demonstrate how these "race conditions" occur. Since
>>>>> any
>>>>> of the users can execute a commit() at any point, I've inserted a
>>>>> method
>>>>> call at various points to simulate the effects of other clients
>>>>> committing to the shared project
>>>>>
>>>>> changeAndCommit(ESLocalProject demoProject)
>>>>>
>>>>> to commit changes to the shared project on the server in between the
>>>>> update, commit and commit retry for the demoProjectCopy.
>>>>>
>>>>> Is there a coding pattern or example code I should adopt to
>>>>> implement a
>>>>> two-way sync capability? Is there any locking feature in EMFStore
>>>>> that I
>>>>> could use to eliminate the potential for the interweaving of update()
>>>>> and commit() calls to the server from concurrent clients? Or perhaps
>>>>> some other way to simulate an atomic sync function?
>>>>>
>>>>> Here's the modified merge example code demonstrating the race
>>>>> condition:
>>>>>
>>>>> package org.eclipse.emf.emfstore.example.merging;
>>>>>
>>>>> import java.io.File;
>>>>> import java.io.IOException;
>>>>> import java.util.List;
>>>>>
>>>>> import org.eclipse.core.runtime.IProgressMonitor;
>>>>> import org.eclipse.core.runtime.NullProgressMonitor;
>>>>> import org.eclipse.emf.emfstore.bowling.BowlingFactory;
>>>>> import org.eclipse.emf.emfstore.bowling.League;
>>>>> import org.eclipse.emf.emfstore.bowling.Player;
>>>>> import org.eclipse.emf.emfstore.client.ESLocalProject;
>>>>> import org.eclipse.emf.emfstore.client.ESServer;
>>>>> import org.eclipse.emf.emfstore.client.ESWorkspace;
>>>>> import org.eclipse.emf.emfstore.client.ESWorkspaceProvider;
>>>>> import org.eclipse.emf.emfstore.client.callbacks.ESUpdateCallback;
>>>>> import
>>>>> org.eclipse.emf.emfstore.client.exceptions.ESServerStartFailedException;
>>>>>
>>>>> import org.eclipse.emf.emfstore.common.ESSystemOutProgressMonitor;
>>>>> import
>>>>> org.eclipse.emf.emfstore.common.model.ESModelElementIdToEObjectMapping;
>>>>>
>>>>> import org.eclipse.emf.emfstore.internal.client.model.Configuration;
>>>>> import
>>>>> org.eclipse.emf.emfstore.internal.client.model.exceptions.ChangeConflictException;
>>>>>
>>>>>
>>>>>
>>>>> import org.eclipse.emf.emfstore.internal.common.model.util.FileUtil;
>>>>> import org.eclipse.emf.emfstore.internal.server.ServerConfiguration;
>>>>> import org.eclipse.emf.emfstore.server.ESConflict;
>>>>> import org.eclipse.emf.emfstore.server.ESConflictSet;
>>>>> import org.eclipse.emf.emfstore.server.exceptions.ESException;
>>>>> import
>>>>> org.eclipse.emf.emfstore.server.exceptions.ESUpdateRequiredException;
>>>>> import org.eclipse.emf.emfstore.server.model.ESChangePackage;
>>>>> import
>>>>> org.eclipse.emf.emfstore.server.model.versionspec.ESVersionSpec;
>>>>> import org.eclipse.equinox.app.IApplication;
>>>>> import org.eclipse.equinox.app.IApplicationContext;
>>>>>
>>>>> /**
>>>>> * An application that runs the demo.<br>
>>>>> * Run a client that shows the merging feature of the EMFstore
>>>>> * Please note: this is the programmatic way of merging
>>>>> * EMFStore also provides a default UI for merging
>>>>> * If there is a problem with the connection to the server
>>>>> * e.g. a network, a specific ESException will be thrown
>>>>> */
>>>>> public class RaceConditionTestApplication implements IApplication {
>>>>>
>>>>> /**
>>>>> * {@inheritDoc}
>>>>> */
>>>>> public Object start(IApplicationContext context) {
>>>>>
>>>>> try {
>>>>> cleanEmfstoreFolders();
>>>>>
>>>>> // Create a client representation for a local server and
>>>>> start a local server.
>>>>> ESServer localServer =
>>>>> ESServer.FACTORY.createAndStartLocalServer();
>>>>>
>>>>> /*
>>>>> * Reuse the client from the hello world example. It
>>>>> will
>>>>> clean up all local and remote projects and create
>>>>> * one project with some content on the server and two
>>>>> checked-out copies of the project on the client.
>>>>> */
>>>>>
>>>>> org.eclipse.emf.emfstore.example.helloworld.Application.runClient(localServer);
>>>>>
>>>>>
>>>>>
>>>>>
>>>>> // We run our own client code to demonstrate merging
>>>>> now.
>>>>> runClient(localServer);
>>>>>
>>>>> } catch (ESServerStartFailedException e) {
>>>>> System.out.println("Server start failed!");
>>>>> e.printStackTrace();
>>>>> } catch (ESException e) {
>>>>> /*
>>>>> * If there is a problem with the connection to the
>>>>> server
>>>>> * e.g. a network, a specific EMFStoreException will be
>>>>> thrown
>>>>> */
>>>>> System.out.println("Connection to Server failed!");
>>>>> e.printStackTrace();
>>>>> }
>>>>> return IApplication.EXIT_OK;
>>>>> }
>>>>>
>>>>> @SuppressWarnings("restriction")
>>>>> private static void cleanEmfstoreFolders() {
>>>>> try {
>>>>> String clientWorkspaceDirectoryString =
>>>>> Configuration.getFileInfo().getWorkspaceDirectory();
>>>>> File clientWorkspaceDirectory = new
>>>>> File(clientWorkspaceDirectoryString);
>>>>> System.out.println("Deleting client workspace
>>>>> directory " +
>>>>> clientWorkspaceDirectory.getAbsolutePath());
>>>>> FileUtil.deleteDirectory(clientWorkspaceDirectory,
>>>>> true);
>>>>>
>>>>> String serverWorkspaceDirectoryString =
>>>>> ServerConfiguration.getLocationProvider().getWorkspaceDirectory();
>>>>> File serverWorkspaceDirectory = new
>>>>> File(serverWorkspaceDirectoryString);
>>>>> System.out.println("Deleting server workspace
>>>>> directory " +
>>>>> serverWorkspaceDirectory.getAbsolutePath());
>>>>> FileUtil.deleteDirectory(serverWorkspaceDirectory,
>>>>> true);
>>>>> } catch (IOException e) {
>>>>> throw new RuntimeException(e);
>>>>> }
>>>>> }
>>>>>
>>>>> public static void runClient(ESServer server) throws
>>>>> ESException {
>>>>> System.out.println("Client starting...");
>>>>>
>>>>> ESWorkspace workspace =
>>>>> ESWorkspaceProvider.INSTANCE.getWorkspace();
>>>>> final ESLocalProject demoProject =
>>>>> workspace.getLocalProjects().get(0);
>>>>> League league = (League)
>>>>> demoProject.getModelElements().get(0);
>>>>> final ESLocalProject demoProjectCopy =
>>>>> workspace.getLocalProjects().get(1);
>>>>> League leagueCopy = (League)
>>>>> demoProjectCopy.getModelElements().get(0);
>>>>>
>>>>> // Change the name of the league in project 1,add a new
>>>>> player
>>>>> and commit the change
>>>>> league.setName("Euro-League");
>>>>> Player newPlayer = BowlingFactory.eINSTANCE.createPlayer();
>>>>> newPlayer.setName("Eugene");
>>>>> league.getPlayers().add(newPlayer);
>>>>>
>>>>> demoProject.commit(new ESSystemOutProgressMonitor());
>>>>>
>>>>> /*
>>>>> * Changing the name again value without calling update() on
>>>>> the
>>>>> copy first will cause a conflict on commit.
>>>>> * We also add one change which is non-conflicting,
>>>>> setting the
>>>>> name of the first player.
>>>>> */
>>>>> leagueCopy.setName("EU-League");
>>>>> leagueCopy.getPlayers().get(0).setName("Johannes");
>>>>>
>>>>> try {
>>>>> demoProjectCopy.commit(new
>>>>> ESSystemOutProgressMonitor());
>>>>> } catch (ESUpdateRequiredException e) {
>>>>> // The commit failed since the other demoProject was
>>>>> committed first and therefore demoProjectCopy needs an
>>>>> // update
>>>>> System.out.println("\nCommit of demoProjectCopy
>>>>> failed.");
>>>>>
>>>>> // We run update in demoProjectCopy with an
>>>>> UpdateCallback
>>>>> to handle conflicts
>>>>> System.out.println("\nUpdate of demoProjectCopy with
>>>>> conflict resolver...");
>>>>>
>>>>> demoProjectCopy.update(ESVersionSpec.FACTORY.createHEAD(),
>>>>> new ESUpdateCallback() {
>>>>>
>>>>> boolean changeMade = false;
>>>>>
>>>>> public void noChangesOnServer() {
>>>>> /*
>>>>> * do nothing if there are no changes on the
>>>>> server
>>>>> (in this example we know
>>>>> * there are changes anyway)
>>>>> */
>>>>> }
>>>>>
>>>>> public boolean inspectChanges(ESLocalProject
>>>>> project,
>>>>> List<ESChangePackage> changes,
>>>>> ESModelElementIdToEObjectMapping
>>>>> idToEObjectMapping) {
>>>>>
>>>>> /*
>>>>> * If another client commits to the project
>>>>> during
>>>>> inspectChanges(), the UpdateController throws
>>>>> * "ChangeConflictException: Conflict
>>>>> detected on
>>>>> update"
>>>>> */
>>>>> if (false) {
>>>>> changeAndCommit(demoProject);
>>>>> }
>>>>> // allow update to proceed, here we could
>>>>> also add
>>>>> some UI
>>>>> return true;
>>>>> }
>>>>>
>>>>> public boolean conflictOccurred(ESConflictSet
>>>>> changeConflictSet, IProgressMonitor monitor) {
>>>>> /*
>>>>> * If another client commits to the project
>>>>> during
>>>>> inspectChanges(), the UpdateController throws
>>>>> * "ChangeConflictException: Conflict
>>>>> detected on
>>>>> update"
>>>>> */
>>>>> if (!changeMade && false) {
>>>>> changeAndCommit(demoProject);
>>>>> changeMade = true;
>>>>> }
>>>>> /*
>>>>> * One or more conflicts have occured, they are
>>>>> delivered in a change conflict set
>>>>> * We know there is only one conflict so we
>>>>> grab it
>>>>> */
>>>>> ESConflict conflict =
>>>>> changeConflictSet.getConflicts().iterator().next();
>>>>>
>>>>> /*
>>>>> * We resolve the conflict by accepting all
>>>>> of the
>>>>> conflicting local operations and rejecting all of
>>>>> * the remote operations. This means that we
>>>>> revert
>>>>> the league name change of demoProject and accept
>>>>> * the league name change of demoProjectCopy.
>>>>> The
>>>>> player name change in demoProject is accepted also
>>>>> * since it was not conflicting with any other
>>>>> change.
>>>>> */
>>>>>
>>>>> conflict.resolveConflict(conflict.getLocalOperations(),
>>>>> conflict.getRemoteOperations());
>>>>> /*
>>>>> * Finally we claim to have resolved all
>>>>> conflicts
>>>>> so update will try to proceed.
>>>>> */
>>>>> return true;
>>>>> }
>>>>> }, new ESSystemOutProgressMonitor());
>>>>>
>>>>> /*
>>>>> * Commits from other clients at this point results in
>>>>> another
>>>>> * "ESUpdateRequiredException: BaseVersion outdated,
>>>>> please
>>>>> update before commit." exception being thrown.
>>>>> */
>>>>> changeAndCommit(demoProject);
>>>>>
>>>>> // commit merge result in project 2
>>>>> System.out.println("\nCommit of merge result of
>>>>> demoProjectCopy");
>>>>> demoProjectCopy.commit(new
>>>>> ESSystemOutProgressMonitor());
>>>>>
>>>>> // After having merged the two projects update local
>>>>> project 1
>>>>> System.out.println("\nUpdate of demoProject");
>>>>> demoProject.update(new NullProgressMonitor());
>>>>>
>>>>> // Finally we print the league and player names of both
>>>>> projects
>>>>> System.out.println("\nLeague name in demoProject is
>>>>> now: "
>>>>> + league.getName());
>>>>> System.out.println("\nLeague name in demoProjectCopy is
>>>>> now: " + leagueCopy.getName());
>>>>> System.out.println("\nPlayer name in demoProject is now:
>>>>> " +
>>>>> league.getPlayers().get(0).getName());
>>>>> System.out.println("\nPlayer name in demoProjectCopy is
>>>>> now:
>>>>> " + leagueCopy.getPlayers().get(0).getName());
>>>>>
>>>>> }
>>>>> }
>>>>>
>>>>> private static void changeAndCommit(ESLocalProject
>>>>> demoProject) {
>>>>> League league = (League)
>>>>> demoProject.getModelElements().get(0);
>>>>> league.setName("Another name");
>>>>> Player newPlayer = BowlingFactory.eINSTANCE.createPlayer();
>>>>> newPlayer.setName("Bill");
>>>>> league.getPlayers().add(newPlayer);
>>>>> try {
>>>>> demoProject.commit(new ESSystemOutProgressMonitor());
>>>>> } catch (ESException e) {
>>>>> // TODO Auto-generated catch block
>>>>> e.printStackTrace();
>>>>> }
>>>>> }
>>>>>
>>>>> public void stop() {
>>>>> // do nothing
>>>>> }
>>>>> }
>>>>
>>>>
>>>
>>
>>
>


--
Maximilian Kögel

Get Professional Eclipse Support: http://eclipsesource.com/munich
Re: [EMFStore] Updates and commits with concurrent users [message #1328152 is a reply to message #1296930] Fri, 02 May 2014 14:43 Go to previous messageGo to next message
Scott Dybiec is currently offline Scott DybiecFriend
Messages: 148
Registered: July 2009
Senior Member
My testing shows that slow connections significantly increase the
likelihood of competing commits, so my application will need this lock.

I'll learn more about the EMFStore server internals and how best to
implement this locking capability.

Can you say a little more about how you envision the time-based lock to
work? Is the timeout you suggest for acquiring the lock or for
restricting the maximum time the lock can be held (an auto-unlock
feature) -- or both?

I'm thinking of four new methods in VersionSubInterfaceImpl something
like this:

acquireLock(String sessionId, String projectId, int acquireTimeout)
releaseLock(String sessionId, String projectId)
acquireLockAndGetChanges(...)
createVersionAndReleaseLock(...)

Any suggestions are welcome.

$cott

On 4/15/2014 5:51 AM, Maximilian Koegel wrote:
> Yes, that is correct. EMFStore is intended for offline usage, like git
> or svn, so typically you will not have many competing commits at the
> same time. The problem with an atomic update and commit is that often
> merging is done interactively so a lock would hold of all other clients.
> I would accept a time-baed lock for a project a additional API though in
> case you would propose a patch. We have no plans to implement something
> like that ourself at his time.
>
> Cheers,
> Maximilian
>
> Am 14.04.2014 15:05, schrieb scott@xxxxxxxx:
>> OK, thanks for the quick response.
>>
>> Without a locking mechanism or a native two-way sync() function, it
>> sounds like I'll need to repeatedly retry the update() and commit()
>> until it succeeds or exceeds a retry limit. Hopefully that will work for
>> the limited number of concurrent users I need to support.
>>
>> Any plans to implement a native sync() or lock mechanism?
>>
>> Thanks,
>>
>> Scott
>>
>>
>> On 4/14/2014 7:24 AM, Maximilian Koegel wrote:
>>> ESUpdateRequiredException means your client´s project is outdated and
>>> needs an Update. You could keep updating but there is no guarantee it
>>> will ever succeed to commit since other clients could keep pushing in
>>> commits. To block other clients from making commits would require
>>> additional API for locking, which is currently not implemented.
>>> ChangeConflictException means your client´s project has conflicting
>>> local changes with an update incoming from the server, which need to be
>>> resolved by merging. They could be auto-merged (conflictOccurred method)
>>> by just dropping one of the changes, if this is something that is
>>> acceptable in your application.
>>>
>>> Cheers,
>>> Maximilian
>>>
>>>
>>>
>>>
>>> Am 14.04.2014 01:56, schrieb scott@xxxxxxxx:
>>>> Yes, your explanation sounds right and I get both
>>>> ESUpdateRequiredException and ChangeConflictException depending where
>>>> the other client update occurs.
>>>>
>>>> For example in the program I attached, if another client commits to the
>>>> project during inspectChanges(), the UpdateController throws
>>>> "ChangeConflictException: Conflict detected on
>>>> update".
>>>>
>>>> Know any solutions to this problem?
>>>>
>>>> $cott
>>>>
>>>>
>>>> On 4/11/2014 4:36 AM, Maximilian Koegel wrote:
>>>>> Hi Scott,
>>>>>
>>>>> if I understand you correctly the "race conditions" are conceptional
>>>>> race conditions not real technical race conditions. They result from
>>>>> the
>>>>> conceptional problem that clients can commit at any time but only if
>>>>> they have updated to the current head version they succeed with their
>>>>> commit. This is since otherwise a merge is required because of
>>>>> concurrent changes. If any client commits in between the other clients
>>>>> update and commit attempt, the other client is forced to update again.
>>>>> The exceptions that you encounter are probably
>>>>> ESUpdateRequiredException, right?
>>>>>
>>>>> Cheers,
>>>>> Maximilian
>>>>>
>>>>>
>>>>> Am 10.04.2014 17:11, schrieb scott@xxxxxxxx:
>>>>>>
>>>>>> My application needs a two-way sync capability in a multi-user (~10
>>>>>> users) environment sharing a common project, but I'm not sure the best
>>>>>> way to implement this. So far I naively implemented 'sync' as a
>>>>>> combination of EMFStore's update() and commit() operations, and I'm
>>>>>> struggling to avoid a race condition I've encountered during testing.
>>>>>>
>>>>>> Since update() and commit() are separate operations, there is room for
>>>>>> another client (Client 2) to commit() in between Client 1's
>>>>>> update() and
>>>>>> commit() operations. update() and commit() are separate but sometimes
>>>>>> dependent on one another and commits to the server can only be applied
>>>>>> once the client and server have a common version of the model -- which
>>>>>> may require an update from the server. Depending on the ordering of
>>>>>> these interweaved update() and commit() calls to the server from
>>>>>> concurrent clients, I've found that exceptions can be thrown that are
>>>>>> hard to recover from as there is no limit to the number times the
>>>>>> exceptions could recur.
>>>>>>
>>>>>> Here's the scenario I've encountered using EMFStore's example merge
>>>>>> application to demonstrate how these "race conditions" occur. Since
>>>>>> any
>>>>>> of the users can execute a commit() at any point, I've inserted a
>>>>>> method
>>>>>> call at various points to simulate the effects of other clients
>>>>>> committing to the shared project
>>>>>>
>>>>>> changeAndCommit(ESLocalProject demoProject)
>>>>>>
>>>>>> to commit changes to the shared project on the server in between the
>>>>>> update, commit and commit retry for the demoProjectCopy.
>>>>>>
>>>>>> Is there a coding pattern or example code I should adopt to
>>>>>> implement a
>>>>>> two-way sync capability? Is there any locking feature in EMFStore
>>>>>> that I
>>>>>> could use to eliminate the potential for the interweaving of update()
>>>>>> and commit() calls to the server from concurrent clients? Or perhaps
>>>>>> some other way to simulate an atomic sync function?
>>>>>>
>>>>>> Here's the modified merge example code demonstrating the race
>>>>>> condition:
>>>>>>
>>>>>> package org.eclipse.emf.emfstore.example.merging;
>>>>>>
>>>>>> import java.io.File;
>>>>>> import java.io.IOException;
>>>>>> import java.util.List;
>>>>>>
>>>>>> import org.eclipse.core.runtime.IProgressMonitor;
>>>>>> import org.eclipse.core.runtime.NullProgressMonitor;
>>>>>> import org.eclipse.emf.emfstore.bowling.BowlingFactory;
>>>>>> import org.eclipse.emf.emfstore.bowling.League;
>>>>>> import org.eclipse.emf.emfstore.bowling.Player;
>>>>>> import org.eclipse.emf.emfstore.client.ESLocalProject;
>>>>>> import org.eclipse.emf.emfstore.client.ESServer;
>>>>>> import org.eclipse.emf.emfstore.client.ESWorkspace;
>>>>>> import org.eclipse.emf.emfstore.client.ESWorkspaceProvider;
>>>>>> import org.eclipse.emf.emfstore.client.callbacks.ESUpdateCallback;
>>>>>> import
>>>>>> org.eclipse.emf.emfstore.client.exceptions.ESServerStartFailedException;
>>>>>>
>>>>>> import org.eclipse.emf.emfstore.common.ESSystemOutProgressMonitor;
>>>>>> import
>>>>>> org.eclipse.emf.emfstore.common.model.ESModelElementIdToEObjectMapping;
>>>>>>
>>>>>> import org.eclipse.emf.emfstore.internal.client.model.Configuration;
>>>>>> import
>>>>>> org.eclipse.emf.emfstore.internal.client.model.exceptions.ChangeConflictException;
>>>>>>
>>>>>>
>>>>>>
>>>>>> import org.eclipse.emf.emfstore.internal.common.model.util.FileUtil;
>>>>>> import org.eclipse.emf.emfstore.internal.server.ServerConfiguration;
>>>>>> import org.eclipse.emf.emfstore.server.ESConflict;
>>>>>> import org.eclipse.emf.emfstore.server.ESConflictSet;
>>>>>> import org.eclipse.emf.emfstore.server.exceptions.ESException;
>>>>>> import
>>>>>> org.eclipse.emf.emfstore.server.exceptions.ESUpdateRequiredException;
>>>>>> import org.eclipse.emf.emfstore.server.model.ESChangePackage;
>>>>>> import
>>>>>> org.eclipse.emf.emfstore.server.model.versionspec.ESVersionSpec;
>>>>>> import org.eclipse.equinox.app.IApplication;
>>>>>> import org.eclipse.equinox.app.IApplicationContext;
>>>>>>
>>>>>> /**
>>>>>> * An application that runs the demo.<br>
>>>>>> * Run a client that shows the merging feature of the EMFstore
>>>>>> * Please note: this is the programmatic way of merging
>>>>>> * EMFStore also provides a default UI for merging
>>>>>> * If there is a problem with the connection to the server
>>>>>> * e.g. a network, a specific ESException will be thrown
>>>>>> */
>>>>>> public class RaceConditionTestApplication implements IApplication {
>>>>>>
>>>>>> /**
>>>>>> * {@inheritDoc}
>>>>>> */
>>>>>> public Object start(IApplicationContext context) {
>>>>>>
>>>>>> try {
>>>>>> cleanEmfstoreFolders();
>>>>>>
>>>>>> // Create a client representation for a local server and
>>>>>> start a local server.
>>>>>> ESServer localServer =
>>>>>> ESServer.FACTORY.createAndStartLocalServer();
>>>>>>
>>>>>> /*
>>>>>> * Reuse the client from the hello world example. It
>>>>>> will
>>>>>> clean up all local and remote projects and create
>>>>>> * one project with some content on the server and two
>>>>>> checked-out copies of the project on the client.
>>>>>> */
>>>>>>
>>>>>> org.eclipse.emf.emfstore.example.helloworld.Application.runClient(localServer);
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>> // We run our own client code to demonstrate merging
>>>>>> now.
>>>>>> runClient(localServer);
>>>>>>
>>>>>> } catch (ESServerStartFailedException e) {
>>>>>> System.out.println("Server start failed!");
>>>>>> e.printStackTrace();
>>>>>> } catch (ESException e) {
>>>>>> /*
>>>>>> * If there is a problem with the connection to the
>>>>>> server
>>>>>> * e.g. a network, a specific EMFStoreException will be
>>>>>> thrown
>>>>>> */
>>>>>> System.out.println("Connection to Server failed!");
>>>>>> e.printStackTrace();
>>>>>> }
>>>>>> return IApplication.EXIT_OK;
>>>>>> }
>>>>>>
>>>>>> @SuppressWarnings("restriction")
>>>>>> private static void cleanEmfstoreFolders() {
>>>>>> try {
>>>>>> String clientWorkspaceDirectoryString =
>>>>>> Configuration.getFileInfo().getWorkspaceDirectory();
>>>>>> File clientWorkspaceDirectory = new
>>>>>> File(clientWorkspaceDirectoryString);
>>>>>> System.out.println("Deleting client workspace
>>>>>> directory " +
>>>>>> clientWorkspaceDirectory.getAbsolutePath());
>>>>>> FileUtil.deleteDirectory(clientWorkspaceDirectory,
>>>>>> true);
>>>>>>
>>>>>> String serverWorkspaceDirectoryString =
>>>>>> ServerConfiguration.getLocationProvider().getWorkspaceDirectory();
>>>>>> File serverWorkspaceDirectory = new
>>>>>> File(serverWorkspaceDirectoryString);
>>>>>> System.out.println("Deleting server workspace
>>>>>> directory " +
>>>>>> serverWorkspaceDirectory.getAbsolutePath());
>>>>>> FileUtil.deleteDirectory(serverWorkspaceDirectory,
>>>>>> true);
>>>>>> } catch (IOException e) {
>>>>>> throw new RuntimeException(e);
>>>>>> }
>>>>>> }
>>>>>>
>>>>>> public static void runClient(ESServer server) throws
>>>>>> ESException {
>>>>>> System.out.println("Client starting...");
>>>>>>
>>>>>> ESWorkspace workspace =
>>>>>> ESWorkspaceProvider.INSTANCE.getWorkspace();
>>>>>> final ESLocalProject demoProject =
>>>>>> workspace.getLocalProjects().get(0);
>>>>>> League league = (League)
>>>>>> demoProject.getModelElements().get(0);
>>>>>> final ESLocalProject demoProjectCopy =
>>>>>> workspace.getLocalProjects().get(1);
>>>>>> League leagueCopy = (League)
>>>>>> demoProjectCopy.getModelElements().get(0);
>>>>>>
>>>>>> // Change the name of the league in project 1,add a new
>>>>>> player
>>>>>> and commit the change
>>>>>> league.setName("Euro-League");
>>>>>> Player newPlayer = BowlingFactory.eINSTANCE.createPlayer();
>>>>>> newPlayer.setName("Eugene");
>>>>>> league.getPlayers().add(newPlayer);
>>>>>>
>>>>>> demoProject.commit(new ESSystemOutProgressMonitor());
>>>>>>
>>>>>> /*
>>>>>> * Changing the name again value without calling update() on
>>>>>> the
>>>>>> copy first will cause a conflict on commit.
>>>>>> * We also add one change which is non-conflicting,
>>>>>> setting the
>>>>>> name of the first player.
>>>>>> */
>>>>>> leagueCopy.setName("EU-League");
>>>>>> leagueCopy.getPlayers().get(0).setName("Johannes");
>>>>>>
>>>>>> try {
>>>>>> demoProjectCopy.commit(new
>>>>>> ESSystemOutProgressMonitor());
>>>>>> } catch (ESUpdateRequiredException e) {
>>>>>> // The commit failed since the other demoProject was
>>>>>> committed first and therefore demoProjectCopy needs an
>>>>>> // update
>>>>>> System.out.println("\nCommit of demoProjectCopy
>>>>>> failed.");
>>>>>>
>>>>>> // We run update in demoProjectCopy with an
>>>>>> UpdateCallback
>>>>>> to handle conflicts
>>>>>> System.out.println("\nUpdate of demoProjectCopy with
>>>>>> conflict resolver...");
>>>>>>
>>>>>> demoProjectCopy.update(ESVersionSpec.FACTORY.createHEAD(),
>>>>>> new ESUpdateCallback() {
>>>>>>
>>>>>> boolean changeMade = false;
>>>>>>
>>>>>> public void noChangesOnServer() {
>>>>>> /*
>>>>>> * do nothing if there are no changes on the
>>>>>> server
>>>>>> (in this example we know
>>>>>> * there are changes anyway)
>>>>>> */
>>>>>> }
>>>>>>
>>>>>> public boolean inspectChanges(ESLocalProject
>>>>>> project,
>>>>>> List<ESChangePackage> changes,
>>>>>> ESModelElementIdToEObjectMapping
>>>>>> idToEObjectMapping) {
>>>>>>
>>>>>> /*
>>>>>> * If another client commits to the project
>>>>>> during
>>>>>> inspectChanges(), the UpdateController throws
>>>>>> * "ChangeConflictException: Conflict
>>>>>> detected on
>>>>>> update"
>>>>>> */
>>>>>> if (false) {
>>>>>> changeAndCommit(demoProject);
>>>>>> }
>>>>>> // allow update to proceed, here we could
>>>>>> also add
>>>>>> some UI
>>>>>> return true;
>>>>>> }
>>>>>>
>>>>>> public boolean conflictOccurred(ESConflictSet
>>>>>> changeConflictSet, IProgressMonitor monitor) {
>>>>>> /*
>>>>>> * If another client commits to the project
>>>>>> during
>>>>>> inspectChanges(), the UpdateController throws
>>>>>> * "ChangeConflictException: Conflict
>>>>>> detected on
>>>>>> update"
>>>>>> */
>>>>>> if (!changeMade && false) {
>>>>>> changeAndCommit(demoProject);
>>>>>> changeMade = true;
>>>>>> }
>>>>>> /*
>>>>>> * One or more conflicts have occured, they are
>>>>>> delivered in a change conflict set
>>>>>> * We know there is only one conflict so we
>>>>>> grab it
>>>>>> */
>>>>>> ESConflict conflict =
>>>>>> changeConflictSet.getConflicts().iterator().next();
>>>>>>
>>>>>> /*
>>>>>> * We resolve the conflict by accepting all
>>>>>> of the
>>>>>> conflicting local operations and rejecting all of
>>>>>> * the remote operations. This means that we
>>>>>> revert
>>>>>> the league name change of demoProject and accept
>>>>>> * the league name change of demoProjectCopy.
>>>>>> The
>>>>>> player name change in demoProject is accepted also
>>>>>> * since it was not conflicting with any other
>>>>>> change.
>>>>>> */
>>>>>>
>>>>>> conflict.resolveConflict(conflict.getLocalOperations(),
>>>>>> conflict.getRemoteOperations());
>>>>>> /*
>>>>>> * Finally we claim to have resolved all
>>>>>> conflicts
>>>>>> so update will try to proceed.
>>>>>> */
>>>>>> return true;
>>>>>> }
>>>>>> }, new ESSystemOutProgressMonitor());
>>>>>>
>>>>>> /*
>>>>>> * Commits from other clients at this point results in
>>>>>> another
>>>>>> * "ESUpdateRequiredException: BaseVersion outdated,
>>>>>> please
>>>>>> update before commit." exception being thrown.
>>>>>> */
>>>>>> changeAndCommit(demoProject);
>>>>>>
>>>>>> // commit merge result in project 2
>>>>>> System.out.println("\nCommit of merge result of
>>>>>> demoProjectCopy");
>>>>>> demoProjectCopy.commit(new
>>>>>> ESSystemOutProgressMonitor());
>>>>>>
>>>>>> // After having merged the two projects update local
>>>>>> project 1
>>>>>> System.out.println("\nUpdate of demoProject");
>>>>>> demoProject.update(new NullProgressMonitor());
>>>>>>
>>>>>> // Finally we print the league and player names of both
>>>>>> projects
>>>>>> System.out.println("\nLeague name in demoProject is
>>>>>> now: "
>>>>>> + league.getName());
>>>>>> System.out.println("\nLeague name in demoProjectCopy is
>>>>>> now: " + leagueCopy.getName());
>>>>>> System.out.println("\nPlayer name in demoProject is now:
>>>>>> " +
>>>>>> league.getPlayers().get(0).getName());
>>>>>> System.out.println("\nPlayer name in demoProjectCopy is
>>>>>> now:
>>>>>> " + leagueCopy.getPlayers().get(0).getName());
>>>>>>
>>>>>> }
>>>>>> }
>>>>>>
>>>>>> private static void changeAndCommit(ESLocalProject
>>>>>> demoProject) {
>>>>>> League league = (League)
>>>>>> demoProject.getModelElements().get(0);
>>>>>> league.setName("Another name");
>>>>>> Player newPlayer = BowlingFactory.eINSTANCE.createPlayer();
>>>>>> newPlayer.setName("Bill");
>>>>>> league.getPlayers().add(newPlayer);
>>>>>> try {
>>>>>> demoProject.commit(new ESSystemOutProgressMonitor());
>>>>>> } catch (ESException e) {
>>>>>> // TODO Auto-generated catch block
>>>>>> e.printStackTrace();
>>>>>> }
>>>>>> }
>>>>>>
>>>>>> public void stop() {
>>>>>> // do nothing
>>>>>> }
>>>>>> }
>>>>>
>>>>>
>>>>
>>>
>>>
>>
>
>
Re: [EMFStore] Updates and commits with concurrent users [message #1336051 is a reply to message #1328152] Tue, 06 May 2014 07:59 Go to previous messageGo to next message
Maximilian Koegel is currently offline Maximilian KoegelFriend
Messages: 253
Registered: July 2009
Senior Member
Hi Scott,

what you propose is what I expected, although I would only offer two
methods:
acquireLock(String sessionId, String projectId, int acquireTimeout)
releaseLock(String sessionId, String projectId)
To my understanding the other methods could be offered on the client
side as convenience but are not required on server side.
What do you think?

Cheers,
Maximilian

P.S.: We also offer professional services in case you would like to
delegate the implementation to us ;).

Am 02.05.2014 16:43, schrieb scott@xxxxxxxx:
> My testing shows that slow connections significantly increase the
> likelihood of competing commits, so my application will need this lock.
>
> I'll learn more about the EMFStore server internals and how best to
> implement this locking capability.
>
> Can you say a little more about how you envision the time-based lock to
> work? Is the timeout you suggest for acquiring the lock or for
> restricting the maximum time the lock can be held (an auto-unlock
> feature) -- or both?
>
> I'm thinking of four new methods in VersionSubInterfaceImpl something
> like this:
>
> acquireLock(String sessionId, String projectId, int acquireTimeout)
> releaseLock(String sessionId, String projectId)
> acquireLockAndGetChanges(...)
> createVersionAndReleaseLock(...)
>
> Any suggestions are welcome.
>
> $cott
>
> On 4/15/2014 5:51 AM, Maximilian Koegel wrote:
>> Yes, that is correct. EMFStore is intended for offline usage, like git
>> or svn, so typically you will not have many competing commits at the
>> same time. The problem with an atomic update and commit is that often
>> merging is done interactively so a lock would hold of all other clients.
>> I would accept a time-baed lock for a project a additional API though in
>> case you would propose a patch. We have no plans to implement something
>> like that ourself at his time.
>>
>> Cheers,
>> Maximilian
>>
>> Am 14.04.2014 15:05, schrieb scott@xxxxxxxx:
>>> OK, thanks for the quick response.
>>>
>>> Without a locking mechanism or a native two-way sync() function, it
>>> sounds like I'll need to repeatedly retry the update() and commit()
>>> until it succeeds or exceeds a retry limit. Hopefully that will work for
>>> the limited number of concurrent users I need to support.
>>>
>>> Any plans to implement a native sync() or lock mechanism?
>>>
>>> Thanks,
>>>
>>> Scott
>>>
>>>
>>> On 4/14/2014 7:24 AM, Maximilian Koegel wrote:
>>>> ESUpdateRequiredException means your client´s project is outdated and
>>>> needs an Update. You could keep updating but there is no guarantee it
>>>> will ever succeed to commit since other clients could keep pushing in
>>>> commits. To block other clients from making commits would require
>>>> additional API for locking, which is currently not implemented.
>>>> ChangeConflictException means your client´s project has conflicting
>>>> local changes with an update incoming from the server, which need to be
>>>> resolved by merging. They could be auto-merged (conflictOccurred
>>>> method)
>>>> by just dropping one of the changes, if this is something that is
>>>> acceptable in your application.
>>>>
>>>> Cheers,
>>>> Maximilian
>>>>
>>>>
>>>>
>>>>
>>>> Am 14.04.2014 01:56, schrieb scott@xxxxxxxx:
>>>>> Yes, your explanation sounds right and I get both
>>>>> ESUpdateRequiredException and ChangeConflictException depending where
>>>>> the other client update occurs.
>>>>>
>>>>> For example in the program I attached, if another client commits to
>>>>> the
>>>>> project during inspectChanges(), the UpdateController throws
>>>>> "ChangeConflictException: Conflict detected on
>>>>> update".
>>>>>
>>>>> Know any solutions to this problem?
>>>>>
>>>>> $cott
>>>>>
>>>>>
>>>>> On 4/11/2014 4:36 AM, Maximilian Koegel wrote:
>>>>>> Hi Scott,
>>>>>>
>>>>>> if I understand you correctly the "race conditions" are conceptional
>>>>>> race conditions not real technical race conditions. They result from
>>>>>> the
>>>>>> conceptional problem that clients can commit at any time but only if
>>>>>> they have updated to the current head version they succeed with their
>>>>>> commit. This is since otherwise a merge is required because of
>>>>>> concurrent changes. If any client commits in between the other
>>>>>> clients
>>>>>> update and commit attempt, the other client is forced to update
>>>>>> again.
>>>>>> The exceptions that you encounter are probably
>>>>>> ESUpdateRequiredException, right?
>>>>>>
>>>>>> Cheers,
>>>>>> Maximilian
>>>>>>
>>>>>>
>>>>>> Am 10.04.2014 17:11, schrieb scott@xxxxxxxx:
>>>>>>>
>>>>>>> My application needs a two-way sync capability in a multi-user (~10
>>>>>>> users) environment sharing a common project, but I'm not sure the
>>>>>>> best
>>>>>>> way to implement this. So far I naively implemented 'sync' as a
>>>>>>> combination of EMFStore's update() and commit() operations, and I'm
>>>>>>> struggling to avoid a race condition I've encountered during
>>>>>>> testing.
>>>>>>>
>>>>>>> Since update() and commit() are separate operations, there is
>>>>>>> room for
>>>>>>> another client (Client 2) to commit() in between Client 1's
>>>>>>> update() and
>>>>>>> commit() operations. update() and commit() are separate but
>>>>>>> sometimes
>>>>>>> dependent on one another and commits to the server can only be
>>>>>>> applied
>>>>>>> once the client and server have a common version of the model --
>>>>>>> which
>>>>>>> may require an update from the server. Depending on the ordering of
>>>>>>> these interweaved update() and commit() calls to the server from
>>>>>>> concurrent clients, I've found that exceptions can be thrown that
>>>>>>> are
>>>>>>> hard to recover from as there is no limit to the number times the
>>>>>>> exceptions could recur.
>>>>>>>
>>>>>>> Here's the scenario I've encountered using EMFStore's example merge
>>>>>>> application to demonstrate how these "race conditions" occur. Since
>>>>>>> any
>>>>>>> of the users can execute a commit() at any point, I've inserted a
>>>>>>> method
>>>>>>> call at various points to simulate the effects of other clients
>>>>>>> committing to the shared project
>>>>>>>
>>>>>>> changeAndCommit(ESLocalProject demoProject)
>>>>>>>
>>>>>>> to commit changes to the shared project on the server in between the
>>>>>>> update, commit and commit retry for the demoProjectCopy.
>>>>>>>
>>>>>>> Is there a coding pattern or example code I should adopt to
>>>>>>> implement a
>>>>>>> two-way sync capability? Is there any locking feature in EMFStore
>>>>>>> that I
>>>>>>> could use to eliminate the potential for the interweaving of
>>>>>>> update()
>>>>>>> and commit() calls to the server from concurrent clients? Or perhaps
>>>>>>> some other way to simulate an atomic sync function?
>>>>>>>
>>>>>>> Here's the modified merge example code demonstrating the race
>>>>>>> condition:
>>>>>>>
>>>>>>> package org.eclipse.emf.emfstore.example.merging;
>>>>>>>
>>>>>>> import java.io.File;
>>>>>>> import java.io.IOException;
>>>>>>> import java.util.List;
>>>>>>>
>>>>>>> import org.eclipse.core.runtime.IProgressMonitor;
>>>>>>> import org.eclipse.core.runtime.NullProgressMonitor;
>>>>>>> import org.eclipse.emf.emfstore.bowling.BowlingFactory;
>>>>>>> import org.eclipse.emf.emfstore.bowling.League;
>>>>>>> import org.eclipse.emf.emfstore.bowling.Player;
>>>>>>> import org.eclipse.emf.emfstore.client.ESLocalProject;
>>>>>>> import org.eclipse.emf.emfstore.client.ESServer;
>>>>>>> import org.eclipse.emf.emfstore.client.ESWorkspace;
>>>>>>> import org.eclipse.emf.emfstore.client.ESWorkspaceProvider;
>>>>>>> import org.eclipse.emf.emfstore.client.callbacks.ESUpdateCallback;
>>>>>>> import
>>>>>>> org.eclipse.emf.emfstore.client.exceptions.ESServerStartFailedException;
>>>>>>>
>>>>>>>
>>>>>>> import org.eclipse.emf.emfstore.common.ESSystemOutProgressMonitor;
>>>>>>> import
>>>>>>> org.eclipse.emf.emfstore.common.model.ESModelElementIdToEObjectMapping;
>>>>>>>
>>>>>>>
>>>>>>> import org.eclipse.emf.emfstore.internal.client.model.Configuration;
>>>>>>> import
>>>>>>> org.eclipse.emf.emfstore.internal.client.model.exceptions.ChangeConflictException;
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> import org.eclipse.emf.emfstore.internal.common.model.util.FileUtil;
>>>>>>> import org.eclipse.emf.emfstore.internal.server.ServerConfiguration;
>>>>>>> import org.eclipse.emf.emfstore.server.ESConflict;
>>>>>>> import org.eclipse.emf.emfstore.server.ESConflictSet;
>>>>>>> import org.eclipse.emf.emfstore.server.exceptions.ESException;
>>>>>>> import
>>>>>>> org.eclipse.emf.emfstore.server.exceptions.ESUpdateRequiredException;
>>>>>>>
>>>>>>> import org.eclipse.emf.emfstore.server.model.ESChangePackage;
>>>>>>> import
>>>>>>> org.eclipse.emf.emfstore.server.model.versionspec.ESVersionSpec;
>>>>>>> import org.eclipse.equinox.app.IApplication;
>>>>>>> import org.eclipse.equinox.app.IApplicationContext;
>>>>>>>
>>>>>>> /**
>>>>>>> * An application that runs the demo.<br>
>>>>>>> * Run a client that shows the merging feature of the EMFstore
>>>>>>> * Please note: this is the programmatic way of merging
>>>>>>> * EMFStore also provides a default UI for merging
>>>>>>> * If there is a problem with the connection to the server
>>>>>>> * e.g. a network, a specific ESException will be thrown
>>>>>>> */
>>>>>>> public class RaceConditionTestApplication implements IApplication {
>>>>>>>
>>>>>>> /**
>>>>>>> * {@inheritDoc}
>>>>>>> */
>>>>>>> public Object start(IApplicationContext context) {
>>>>>>>
>>>>>>> try {
>>>>>>> cleanEmfstoreFolders();
>>>>>>>
>>>>>>> // Create a client representation for a local
>>>>>>> server and
>>>>>>> start a local server.
>>>>>>> ESServer localServer =
>>>>>>> ESServer.FACTORY.createAndStartLocalServer();
>>>>>>>
>>>>>>> /*
>>>>>>> * Reuse the client from the hello world example. It
>>>>>>> will
>>>>>>> clean up all local and remote projects and create
>>>>>>> * one project with some content on the server and
>>>>>>> two
>>>>>>> checked-out copies of the project on the client.
>>>>>>> */
>>>>>>>
>>>>>>> org.eclipse.emf.emfstore.example.helloworld.Application.runClient(localServer);
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> // We run our own client code to demonstrate merging
>>>>>>> now.
>>>>>>> runClient(localServer);
>>>>>>>
>>>>>>> } catch (ESServerStartFailedException e) {
>>>>>>> System.out.println("Server start failed!");
>>>>>>> e.printStackTrace();
>>>>>>> } catch (ESException e) {
>>>>>>> /*
>>>>>>> * If there is a problem with the connection to the
>>>>>>> server
>>>>>>> * e.g. a network, a specific EMFStoreException
>>>>>>> will be
>>>>>>> thrown
>>>>>>> */
>>>>>>> System.out.println("Connection to Server failed!");
>>>>>>> e.printStackTrace();
>>>>>>> }
>>>>>>> return IApplication.EXIT_OK;
>>>>>>> }
>>>>>>>
>>>>>>> @SuppressWarnings("restriction")
>>>>>>> private static void cleanEmfstoreFolders() {
>>>>>>> try {
>>>>>>> String clientWorkspaceDirectoryString =
>>>>>>> Configuration.getFileInfo().getWorkspaceDirectory();
>>>>>>> File clientWorkspaceDirectory = new
>>>>>>> File(clientWorkspaceDirectoryString);
>>>>>>> System.out.println("Deleting client workspace
>>>>>>> directory " +
>>>>>>> clientWorkspaceDirectory.getAbsolutePath());
>>>>>>> FileUtil.deleteDirectory(clientWorkspaceDirectory,
>>>>>>> true);
>>>>>>>
>>>>>>> String serverWorkspaceDirectoryString =
>>>>>>> ServerConfiguration.getLocationProvider().getWorkspaceDirectory();
>>>>>>> File serverWorkspaceDirectory = new
>>>>>>> File(serverWorkspaceDirectoryString);
>>>>>>> System.out.println("Deleting server workspace
>>>>>>> directory " +
>>>>>>> serverWorkspaceDirectory.getAbsolutePath());
>>>>>>> FileUtil.deleteDirectory(serverWorkspaceDirectory,
>>>>>>> true);
>>>>>>> } catch (IOException e) {
>>>>>>> throw new RuntimeException(e);
>>>>>>> }
>>>>>>> }
>>>>>>>
>>>>>>> public static void runClient(ESServer server) throws
>>>>>>> ESException {
>>>>>>> System.out.println("Client starting...");
>>>>>>>
>>>>>>> ESWorkspace workspace =
>>>>>>> ESWorkspaceProvider.INSTANCE.getWorkspace();
>>>>>>> final ESLocalProject demoProject =
>>>>>>> workspace.getLocalProjects().get(0);
>>>>>>> League league = (League)
>>>>>>> demoProject.getModelElements().get(0);
>>>>>>> final ESLocalProject demoProjectCopy =
>>>>>>> workspace.getLocalProjects().get(1);
>>>>>>> League leagueCopy = (League)
>>>>>>> demoProjectCopy.getModelElements().get(0);
>>>>>>>
>>>>>>> // Change the name of the league in project 1,add a new
>>>>>>> player
>>>>>>> and commit the change
>>>>>>> league.setName("Euro-League");
>>>>>>> Player newPlayer =
>>>>>>> BowlingFactory.eINSTANCE.createPlayer();
>>>>>>> newPlayer.setName("Eugene");
>>>>>>> league.getPlayers().add(newPlayer);
>>>>>>>
>>>>>>> demoProject.commit(new ESSystemOutProgressMonitor());
>>>>>>>
>>>>>>> /*
>>>>>>> * Changing the name again value without calling
>>>>>>> update() on
>>>>>>> the
>>>>>>> copy first will cause a conflict on commit.
>>>>>>> * We also add one change which is non-conflicting,
>>>>>>> setting the
>>>>>>> name of the first player.
>>>>>>> */
>>>>>>> leagueCopy.setName("EU-League");
>>>>>>> leagueCopy.getPlayers().get(0).setName("Johannes");
>>>>>>>
>>>>>>> try {
>>>>>>> demoProjectCopy.commit(new
>>>>>>> ESSystemOutProgressMonitor());
>>>>>>> } catch (ESUpdateRequiredException e) {
>>>>>>> // The commit failed since the other demoProject was
>>>>>>> committed first and therefore demoProjectCopy needs an
>>>>>>> // update
>>>>>>> System.out.println("\nCommit of demoProjectCopy
>>>>>>> failed.");
>>>>>>>
>>>>>>> // We run update in demoProjectCopy with an
>>>>>>> UpdateCallback
>>>>>>> to handle conflicts
>>>>>>> System.out.println("\nUpdate of demoProjectCopy with
>>>>>>> conflict resolver...");
>>>>>>>
>>>>>>> demoProjectCopy.update(ESVersionSpec.FACTORY.createHEAD(),
>>>>>>> new ESUpdateCallback() {
>>>>>>>
>>>>>>> boolean changeMade = false;
>>>>>>>
>>>>>>> public void noChangesOnServer() {
>>>>>>> /*
>>>>>>> * do nothing if there are no changes on the
>>>>>>> server
>>>>>>> (in this example we know
>>>>>>> * there are changes anyway)
>>>>>>> */
>>>>>>> }
>>>>>>>
>>>>>>> public boolean inspectChanges(ESLocalProject
>>>>>>> project,
>>>>>>> List<ESChangePackage> changes,
>>>>>>> ESModelElementIdToEObjectMapping
>>>>>>> idToEObjectMapping) {
>>>>>>>
>>>>>>> /*
>>>>>>> * If another client commits to the project
>>>>>>> during
>>>>>>> inspectChanges(), the UpdateController throws
>>>>>>> * "ChangeConflictException: Conflict
>>>>>>> detected on
>>>>>>> update"
>>>>>>> */
>>>>>>> if (false) {
>>>>>>> changeAndCommit(demoProject);
>>>>>>> }
>>>>>>> // allow update to proceed, here we could
>>>>>>> also add
>>>>>>> some UI
>>>>>>> return true;
>>>>>>> }
>>>>>>>
>>>>>>> public boolean conflictOccurred(ESConflictSet
>>>>>>> changeConflictSet, IProgressMonitor monitor) {
>>>>>>> /*
>>>>>>> * If another client commits to the project
>>>>>>> during
>>>>>>> inspectChanges(), the UpdateController throws
>>>>>>> * "ChangeConflictException: Conflict
>>>>>>> detected on
>>>>>>> update"
>>>>>>> */
>>>>>>> if (!changeMade && false) {
>>>>>>> changeAndCommit(demoProject);
>>>>>>> changeMade = true;
>>>>>>> }
>>>>>>> /*
>>>>>>> * One or more conflicts have occured,
>>>>>>> they are
>>>>>>> delivered in a change conflict set
>>>>>>> * We know there is only one conflict so we
>>>>>>> grab it
>>>>>>> */
>>>>>>> ESConflict conflict =
>>>>>>> changeConflictSet.getConflicts().iterator().next();
>>>>>>>
>>>>>>> /*
>>>>>>> * We resolve the conflict by accepting all
>>>>>>> of the
>>>>>>> conflicting local operations and rejecting all of
>>>>>>> * the remote operations. This means that we
>>>>>>> revert
>>>>>>> the league name change of demoProject and accept
>>>>>>> * the league name change of demoProjectCopy.
>>>>>>> The
>>>>>>> player name change in demoProject is accepted also
>>>>>>> * since it was not conflicting with any
>>>>>>> other
>>>>>>> change.
>>>>>>> */
>>>>>>>
>>>>>>> conflict.resolveConflict(conflict.getLocalOperations(),
>>>>>>> conflict.getRemoteOperations());
>>>>>>> /*
>>>>>>> * Finally we claim to have resolved all
>>>>>>> conflicts
>>>>>>> so update will try to proceed.
>>>>>>> */
>>>>>>> return true;
>>>>>>> }
>>>>>>> }, new ESSystemOutProgressMonitor());
>>>>>>>
>>>>>>> /*
>>>>>>> * Commits from other clients at this point
>>>>>>> results in
>>>>>>> another
>>>>>>> * "ESUpdateRequiredException: BaseVersion outdated,
>>>>>>> please
>>>>>>> update before commit." exception being thrown.
>>>>>>> */
>>>>>>> changeAndCommit(demoProject);
>>>>>>>
>>>>>>> // commit merge result in project 2
>>>>>>> System.out.println("\nCommit of merge result of
>>>>>>> demoProjectCopy");
>>>>>>> demoProjectCopy.commit(new
>>>>>>> ESSystemOutProgressMonitor());
>>>>>>>
>>>>>>> // After having merged the two projects update local
>>>>>>> project 1
>>>>>>> System.out.println("\nUpdate of demoProject");
>>>>>>> demoProject.update(new NullProgressMonitor());
>>>>>>>
>>>>>>> // Finally we print the league and player names of
>>>>>>> both
>>>>>>> projects
>>>>>>> System.out.println("\nLeague name in demoProject is
>>>>>>> now: "
>>>>>>> + league.getName());
>>>>>>> System.out.println("\nLeague name in
>>>>>>> demoProjectCopy is
>>>>>>> now: " + leagueCopy.getName());
>>>>>>> System.out.println("\nPlayer name in demoProject
>>>>>>> is now:
>>>>>>> " +
>>>>>>> league.getPlayers().get(0).getName());
>>>>>>> System.out.println("\nPlayer name in
>>>>>>> demoProjectCopy is
>>>>>>> now:
>>>>>>> " + leagueCopy.getPlayers().get(0).getName());
>>>>>>>
>>>>>>> }
>>>>>>> }
>>>>>>>
>>>>>>> private static void changeAndCommit(ESLocalProject
>>>>>>> demoProject) {
>>>>>>> League league = (League)
>>>>>>> demoProject.getModelElements().get(0);
>>>>>>> league.setName("Another name");
>>>>>>> Player newPlayer =
>>>>>>> BowlingFactory.eINSTANCE.createPlayer();
>>>>>>> newPlayer.setName("Bill");
>>>>>>> league.getPlayers().add(newPlayer);
>>>>>>> try {
>>>>>>> demoProject.commit(new ESSystemOutProgressMonitor());
>>>>>>> } catch (ESException e) {
>>>>>>> // TODO Auto-generated catch block
>>>>>>> e.printStackTrace();
>>>>>>> }
>>>>>>> }
>>>>>>>
>>>>>>> public void stop() {
>>>>>>> // do nothing
>>>>>>> }
>>>>>>> }
>>>>>>
>>>>>>
>>>>>
>>>>
>>>>
>>>
>>
>>
>


--
Maximilian Kögel

Get Professional Eclipse Support: http://eclipsesource.com/munich
Re: [EMFStore] Updates and commits with concurrent users [message #1337168 is a reply to message #1336051] Tue, 06 May 2014 19:00 Go to previous messageGo to next message
Scott Dybiec is currently offline Scott DybiecFriend
Messages: 148
Registered: July 2009
Senior Member
The primary advantage of combining the locking and versioning methods
into one server side XML-RPC service is performance. Doing so reduces
the number of round trips to the server and minimizes the time the lock
is held. Since the current EMFStore implementation is based on the
XML-RPC WebServer, a new TCP/IP connection is built and torn down for
every request and this is somewhat expensive over a cell network.

Since I really don't know the performance impact of implementing the
composed lock/update & commit/unlock services vs. making individual
calls for each one, it's hard to make a case one way or another.

Let's let the data tell us what to do. Once the locking functions are
built, I can performance test client-side and service-side
implementations of the composed services and decide whether adding the
additional methods to the core API is justified.

Sound OK?

Scott

On 5/6/2014 3:59 AM, Maximilian Koegel wrote:
> Hi Scott,
>
> what you propose is what I expected, although I would only offer two
> methods:
> acquireLock(String sessionId, String projectId, int acquireTimeout)
> releaseLock(String sessionId, String projectId)
> To my understanding the other methods could be offered on the client
> side as convenience but are not required on server side.
> What do you think?
>
> Cheers,
> Maximilian
>
> P.S.: We also offer professional services in case you would like to
> delegate the implementation to us ;).
>
> Am 02.05.2014 16:43, schrieb scott@xxxxxxxx:
>> My testing shows that slow connections significantly increase the
>> likelihood of competing commits, so my application will need this lock.
>>
>> I'll learn more about the EMFStore server internals and how best to
>> implement this locking capability.
>>
>> Can you say a little more about how you envision the time-based lock to
>> work? Is the timeout you suggest for acquiring the lock or for
>> restricting the maximum time the lock can be held (an auto-unlock
>> feature) -- or both?
>>
>> I'm thinking of four new methods in VersionSubInterfaceImpl something
>> like this:
>>
>> acquireLock(String sessionId, String projectId, int acquireTimeout)
>> releaseLock(String sessionId, String projectId)
>> acquireLockAndGetChanges(...)
>> createVersionAndReleaseLock(...)
>>
>> Any suggestions are welcome.
>>
>> $cott
>>
>> On 4/15/2014 5:51 AM, Maximilian Koegel wrote:
>>> Yes, that is correct. EMFStore is intended for offline usage, like git
>>> or svn, so typically you will not have many competing commits at the
>>> same time. The problem with an atomic update and commit is that often
>>> merging is done interactively so a lock would hold of all other clients.
>>> I would accept a time-baed lock for a project a additional API though in
>>> case you would propose a patch. We have no plans to implement something
>>> like that ourself at his time.
>>>
>>> Cheers,
>>> Maximilian
>>>
>>> Am 14.04.2014 15:05, schrieb scott@xxxxxxxx:
>>>> OK, thanks for the quick response.
>>>>
>>>> Without a locking mechanism or a native two-way sync() function, it
>>>> sounds like I'll need to repeatedly retry the update() and commit()
>>>> until it succeeds or exceeds a retry limit. Hopefully that will work for
>>>> the limited number of concurrent users I need to support.
>>>>
>>>> Any plans to implement a native sync() or lock mechanism?
>>>>
>>>> Thanks,
>>>>
>>>> Scott
>>>>
>>>>
>>>> On 4/14/2014 7:24 AM, Maximilian Koegel wrote:
>>>>> ESUpdateRequiredException means your client´s project is outdated and
>>>>> needs an Update. You could keep updating but there is no guarantee it
>>>>> will ever succeed to commit since other clients could keep pushing in
>>>>> commits. To block other clients from making commits would require
>>>>> additional API for locking, which is currently not implemented.
>>>>> ChangeConflictException means your client´s project has conflicting
>>>>> local changes with an update incoming from the server, which need to be
>>>>> resolved by merging. They could be auto-merged (conflictOccurred
>>>>> method)
>>>>> by just dropping one of the changes, if this is something that is
>>>>> acceptable in your application.
>>>>>
>>>>> Cheers,
>>>>> Maximilian
>>>>>
>>>>>
>>>>>
>>>>>
>>>>> Am 14.04.2014 01:56, schrieb scott@xxxxxxxx:
>>>>>> Yes, your explanation sounds right and I get both
>>>>>> ESUpdateRequiredException and ChangeConflictException depending where
>>>>>> the other client update occurs.
>>>>>>
>>>>>> For example in the program I attached, if another client commits to
>>>>>> the
>>>>>> project during inspectChanges(), the UpdateController throws
>>>>>> "ChangeConflictException: Conflict detected on
>>>>>> update".
>>>>>>
>>>>>> Know any solutions to this problem?
>>>>>>
>>>>>> $cott
>>>>>>
>>>>>>
>>>>>> On 4/11/2014 4:36 AM, Maximilian Koegel wrote:
>>>>>>> Hi Scott,
>>>>>>>
>>>>>>> if I understand you correctly the "race conditions" are conceptional
>>>>>>> race conditions not real technical race conditions. They result from
>>>>>>> the
>>>>>>> conceptional problem that clients can commit at any time but only if
>>>>>>> they have updated to the current head version they succeed with their
>>>>>>> commit. This is since otherwise a merge is required because of
>>>>>>> concurrent changes. If any client commits in between the other
>>>>>>> clients
>>>>>>> update and commit attempt, the other client is forced to update
>>>>>>> again.
>>>>>>> The exceptions that you encounter are probably
>>>>>>> ESUpdateRequiredException, right?
>>>>>>>
>>>>>>> Cheers,
>>>>>>> Maximilian
>>>>>>>
>>>>>>>
>>>>>>> Am 10.04.2014 17:11, schrieb scott@xxxxxxxx:
>>>>>>>>
>>>>>>>> My application needs a two-way sync capability in a multi-user (~10
>>>>>>>> users) environment sharing a common project, but I'm not sure the
>>>>>>>> best
>>>>>>>> way to implement this. So far I naively implemented 'sync' as a
>>>>>>>> combination of EMFStore's update() and commit() operations, and I'm
>>>>>>>> struggling to avoid a race condition I've encountered during
>>>>>>>> testing.
>>>>>>>>
>>>>>>>> Since update() and commit() are separate operations, there is
>>>>>>>> room for
>>>>>>>> another client (Client 2) to commit() in between Client 1's
>>>>>>>> update() and
>>>>>>>> commit() operations. update() and commit() are separate but
>>>>>>>> sometimes
>>>>>>>> dependent on one another and commits to the server can only be
>>>>>>>> applied
>>>>>>>> once the client and server have a common version of the model --
>>>>>>>> which
>>>>>>>> may require an update from the server. Depending on the ordering of
>>>>>>>> these interweaved update() and commit() calls to the server from
>>>>>>>> concurrent clients, I've found that exceptions can be thrown that
>>>>>>>> are
>>>>>>>> hard to recover from as there is no limit to the number times the
>>>>>>>> exceptions could recur.
>>>>>>>>
>>>>>>>> Here's the scenario I've encountered using EMFStore's example merge
>>>>>>>> application to demonstrate how these "race conditions" occur. Since
>>>>>>>> any
>>>>>>>> of the users can execute a commit() at any point, I've inserted a
>>>>>>>> method
>>>>>>>> call at various points to simulate the effects of other clients
>>>>>>>> committing to the shared project
>>>>>>>>
>>>>>>>> changeAndCommit(ESLocalProject demoProject)
>>>>>>>>
>>>>>>>> to commit changes to the shared project on the server in between the
>>>>>>>> update, commit and commit retry for the demoProjectCopy.
>>>>>>>>
>>>>>>>> Is there a coding pattern or example code I should adopt to
>>>>>>>> implement a
>>>>>>>> two-way sync capability? Is there any locking feature in EMFStore
>>>>>>>> that I
>>>>>>>> could use to eliminate the potential for the interweaving of
>>>>>>>> update()
>>>>>>>> and commit() calls to the server from concurrent clients? Or perhaps
>>>>>>>> some other way to simulate an atomic sync function?
>>>>>>>>
>>>>>>>> Here's the modified merge example code demonstrating the race
>>>>>>>> condition:
>>>>>>>>
>>>>>>>> package org.eclipse.emf.emfstore.example.merging;
>>>>>>>>
>>>>>>>> import java.io.File;
>>>>>>>> import java.io.IOException;
>>>>>>>> import java.util.List;
>>>>>>>>
>>>>>>>> import org.eclipse.core.runtime.IProgressMonitor;
>>>>>>>> import org.eclipse.core.runtime.NullProgressMonitor;
>>>>>>>> import org.eclipse.emf.emfstore.bowling.BowlingFactory;
>>>>>>>> import org.eclipse.emf.emfstore.bowling.League;
>>>>>>>> import org.eclipse.emf.emfstore.bowling.Player;
>>>>>>>> import org.eclipse.emf.emfstore.client.ESLocalProject;
>>>>>>>> import org.eclipse.emf.emfstore.client.ESServer;
>>>>>>>> import org.eclipse.emf.emfstore.client.ESWorkspace;
>>>>>>>> import org.eclipse.emf.emfstore.client.ESWorkspaceProvider;
>>>>>>>> import org.eclipse.emf.emfstore.client.callbacks.ESUpdateCallback;
>>>>>>>> import
>>>>>>>> org.eclipse.emf.emfstore.client.exceptions.ESServerStartFailedException;
>>>>>>>>
>>>>>>>>
>>>>>>>> import org.eclipse.emf.emfstore.common.ESSystemOutProgressMonitor;
>>>>>>>> import
>>>>>>>> org.eclipse.emf.emfstore.common.model.ESModelElementIdToEObjectMapping;
>>>>>>>>
>>>>>>>>
>>>>>>>> import org.eclipse.emf.emfstore.internal.client.model.Configuration;
>>>>>>>> import
>>>>>>>> org.eclipse.emf.emfstore.internal.client.model.exceptions.ChangeConflictException;
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> import org.eclipse.emf.emfstore.internal.common.model.util.FileUtil;
>>>>>>>> import org.eclipse.emf.emfstore.internal.server.ServerConfiguration;
>>>>>>>> import org.eclipse.emf.emfstore.server.ESConflict;
>>>>>>>> import org.eclipse.emf.emfstore.server.ESConflictSet;
>>>>>>>> import org.eclipse.emf.emfstore.server.exceptions.ESException;
>>>>>>>> import
>>>>>>>> org.eclipse.emf.emfstore.server.exceptions.ESUpdateRequiredException;
>>>>>>>>
>>>>>>>> import org.eclipse.emf.emfstore.server.model.ESChangePackage;
>>>>>>>> import
>>>>>>>> org.eclipse.emf.emfstore.server.model.versionspec.ESVersionSpec;
>>>>>>>> import org.eclipse.equinox.app.IApplication;
>>>>>>>> import org.eclipse.equinox.app.IApplicationContext;
>>>>>>>>
>>>>>>>> /**
>>>>>>>> * An application that runs the demo.<br>
>>>>>>>> * Run a client that shows the merging feature of the EMFstore
>>>>>>>> * Please note: this is the programmatic way of merging
>>>>>>>> * EMFStore also provides a default UI for merging
>>>>>>>> * If there is a problem with the connection to the server
>>>>>>>> * e.g. a network, a specific ESException will be thrown
>>>>>>>> */
>>>>>>>> public class RaceConditionTestApplication implements IApplication {
>>>>>>>>
>>>>>>>> /**
>>>>>>>> * {@inheritDoc}
>>>>>>>> */
>>>>>>>> public Object start(IApplicationContext context) {
>>>>>>>>
>>>>>>>> try {
>>>>>>>> cleanEmfstoreFolders();
>>>>>>>>
>>>>>>>> // Create a client representation for a local
>>>>>>>> server and
>>>>>>>> start a local server.
>>>>>>>> ESServer localServer =
>>>>>>>> ESServer.FACTORY.createAndStartLocalServer();
>>>>>>>>
>>>>>>>> /*
>>>>>>>> * Reuse the client from the hello world example. It
>>>>>>>> will
>>>>>>>> clean up all local and remote projects and create
>>>>>>>> * one project with some content on the server and
>>>>>>>> two
>>>>>>>> checked-out copies of the project on the client.
>>>>>>>> */
>>>>>>>>
>>>>>>>> org.eclipse.emf.emfstore.example.helloworld.Application.runClient(localServer);
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> // We run our own client code to demonstrate merging
>>>>>>>> now.
>>>>>>>> runClient(localServer);
>>>>>>>>
>>>>>>>> } catch (ESServerStartFailedException e) {
>>>>>>>> System.out.println("Server start failed!");
>>>>>>>> e.printStackTrace();
>>>>>>>> } catch (ESException e) {
>>>>>>>> /*
>>>>>>>> * If there is a problem with the connection to the
>>>>>>>> server
>>>>>>>> * e.g. a network, a specific EMFStoreException
>>>>>>>> will be
>>>>>>>> thrown
>>>>>>>> */
>>>>>>>> System.out.println("Connection to Server failed!");
>>>>>>>> e.printStackTrace();
>>>>>>>> }
>>>>>>>> return IApplication.EXIT_OK;
>>>>>>>> }
>>>>>>>>
>>>>>>>> @SuppressWarnings("restriction")
>>>>>>>> private static void cleanEmfstoreFolders() {
>>>>>>>> try {
>>>>>>>> String clientWorkspaceDirectoryString =
>>>>>>>> Configuration.getFileInfo().getWorkspaceDirectory();
>>>>>>>> File clientWorkspaceDirectory = new
>>>>>>>> File(clientWorkspaceDirectoryString);
>>>>>>>> System.out.println("Deleting client workspace
>>>>>>>> directory " +
>>>>>>>> clientWorkspaceDirectory.getAbsolutePath());
>>>>>>>> FileUtil.deleteDirectory(clientWorkspaceDirectory,
>>>>>>>> true);
>>>>>>>>
>>>>>>>> String serverWorkspaceDirectoryString =
>>>>>>>> ServerConfiguration.getLocationProvider().getWorkspaceDirectory();
>>>>>>>> File serverWorkspaceDirectory = new
>>>>>>>> File(serverWorkspaceDirectoryString);
>>>>>>>> System.out.println("Deleting server workspace
>>>>>>>> directory " +
>>>>>>>> serverWorkspaceDirectory.getAbsolutePath());
>>>>>>>> FileUtil.deleteDirectory(serverWorkspaceDirectory,
>>>>>>>> true);
>>>>>>>> } catch (IOException e) {
>>>>>>>> throw new RuntimeException(e);
>>>>>>>> }
>>>>>>>> }
>>>>>>>>
>>>>>>>> public static void runClient(ESServer server) throws
>>>>>>>> ESException {
>>>>>>>> System.out.println("Client starting...");
>>>>>>>>
>>>>>>>> ESWorkspace workspace =
>>>>>>>> ESWorkspaceProvider.INSTANCE.getWorkspace();
>>>>>>>> final ESLocalProject demoProject =
>>>>>>>> workspace.getLocalProjects().get(0);
>>>>>>>> League league = (League)
>>>>>>>> demoProject.getModelElements().get(0);
>>>>>>>> final ESLocalProject demoProjectCopy =
>>>>>>>> workspace.getLocalProjects().get(1);
>>>>>>>> League leagueCopy = (League)
>>>>>>>> demoProjectCopy.getModelElements().get(0);
>>>>>>>>
>>>>>>>> // Change the name of the league in project 1,add a new
>>>>>>>> player
>>>>>>>> and commit the change
>>>>>>>> league.setName("Euro-League");
>>>>>>>> Player newPlayer =
>>>>>>>> BowlingFactory.eINSTANCE.createPlayer();
>>>>>>>> newPlayer.setName("Eugene");
>>>>>>>> league.getPlayers().add(newPlayer);
>>>>>>>>
>>>>>>>> demoProject.commit(new ESSystemOutProgressMonitor());
>>>>>>>>
>>>>>>>> /*
>>>>>>>> * Changing the name again value without calling
>>>>>>>> update() on
>>>>>>>> the
>>>>>>>> copy first will cause a conflict on commit.
>>>>>>>> * We also add one change which is non-conflicting,
>>>>>>>> setting the
>>>>>>>> name of the first player.
>>>>>>>> */
>>>>>>>> leagueCopy.setName("EU-League");
>>>>>>>> leagueCopy.getPlayers().get(0).setName("Johannes");
>>>>>>>>
>>>>>>>> try {
>>>>>>>> demoProjectCopy.commit(new
>>>>>>>> ESSystemOutProgressMonitor());
>>>>>>>> } catch (ESUpdateRequiredException e) {
>>>>>>>> // The commit failed since the other demoProject was
>>>>>>>> committed first and therefore demoProjectCopy needs an
>>>>>>>> // update
>>>>>>>> System.out.println("\nCommit of demoProjectCopy
>>>>>>>> failed.");
>>>>>>>>
>>>>>>>> // We run update in demoProjectCopy with an
>>>>>>>> UpdateCallback
>>>>>>>> to handle conflicts
>>>>>>>> System.out.println("\nUpdate of demoProjectCopy with
>>>>>>>> conflict resolver...");
>>>>>>>>
>>>>>>>> demoProjectCopy.update(ESVersionSpec.FACTORY.createHEAD(),
>>>>>>>> new ESUpdateCallback() {
>>>>>>>>
>>>>>>>> boolean changeMade = false;
>>>>>>>>
>>>>>>>> public void noChangesOnServer() {
>>>>>>>> /*
>>>>>>>> * do nothing if there are no changes on the
>>>>>>>> server
>>>>>>>> (in this example we know
>>>>>>>> * there are changes anyway)
>>>>>>>> */
>>>>>>>> }
>>>>>>>>
>>>>>>>> public boolean inspectChanges(ESLocalProject
>>>>>>>> project,
>>>>>>>> List<ESChangePackage> changes,
>>>>>>>> ESModelElementIdToEObjectMapping
>>>>>>>> idToEObjectMapping) {
>>>>>>>>
>>>>>>>> /*
>>>>>>>> * If another client commits to the project
>>>>>>>> during
>>>>>>>> inspectChanges(), the UpdateController throws
>>>>>>>> * "ChangeConflictException: Conflict
>>>>>>>> detected on
>>>>>>>> update"
>>>>>>>> */
>>>>>>>> if (false) {
>>>>>>>> changeAndCommit(demoProject);
>>>>>>>> }
>>>>>>>> // allow update to proceed, here we could
>>>>>>>> also add
>>>>>>>> some UI
>>>>>>>> return true;
>>>>>>>> }
>>>>>>>>
>>>>>>>> public boolean conflictOccurred(ESConflictSet
>>>>>>>> changeConflictSet, IProgressMonitor monitor) {
>>>>>>>> /*
>>>>>>>> * If another client commits to the project
>>>>>>>> during
>>>>>>>> inspectChanges(), the UpdateController throws
>>>>>>>> * "ChangeConflictException: Conflict
>>>>>>>> detected on
>>>>>>>> update"
>>>>>>>> */
>>>>>>>> if (!changeMade && false) {
>>>>>>>> changeAndCommit(demoProject);
>>>>>>>> changeMade = true;
>>>>>>>> }
>>>>>>>> /*
>>>>>>>> * One or more conflicts have occured,
>>>>>>>> they are
>>>>>>>> delivered in a change conflict set
>>>>>>>> * We know there is only one conflict so we
>>>>>>>> grab it
>>>>>>>> */
>>>>>>>> ESConflict conflict =
>>>>>>>> changeConflictSet.getConflicts().iterator().next();
>>>>>>>>
>>>>>>>> /*
>>>>>>>> * We resolve the conflict by accepting all
>>>>>>>> of the
>>>>>>>> conflicting local operations and rejecting all of
>>>>>>>> * the remote operations. This means that we
>>>>>>>> revert
>>>>>>>> the league name change of demoProject and accept
>>>>>>>> * the league name change of demoProjectCopy.
>>>>>>>> The
>>>>>>>> player name change in demoProject is accepted also
>>>>>>>> * since it was not conflicting with any
>>>>>>>> other
>>>>>>>> change.
>>>>>>>> */
>>>>>>>>
>>>>>>>> conflict.resolveConflict(conflict.getLocalOperations(),
>>>>>>>> conflict.getRemoteOperations());
>>>>>>>> /*
>>>>>>>> * Finally we claim to have resolved all
>>>>>>>> conflicts
>>>>>>>> so update will try to proceed.
>>>>>>>> */
>>>>>>>> return true;
>>>>>>>> }
>>>>>>>> }, new ESSystemOutProgressMonitor());
>>>>>>>>
>>>>>>>> /*
>>>>>>>> * Commits from other clients at this point
>>>>>>>> results in
>>>>>>>> another
>>>>>>>> * "ESUpdateRequiredException: BaseVersion outdated,
>>>>>>>> please
>>>>>>>> update before commit." exception being thrown.
>>>>>>>> */
>>>>>>>> changeAndCommit(demoProject);
>>>>>>>>
>>>>>>>> // commit merge result in project 2
>>>>>>>> System.out.println("\nCommit of merge result of
>>>>>>>> demoProjectCopy");
>>>>>>>> demoProjectCopy.commit(new
>>>>>>>> ESSystemOutProgressMonitor());
>>>>>>>>
>>>>>>>> // After having merged the two projects update local
>>>>>>>> project 1
>>>>>>>> System.out.println("\nUpdate of demoProject");
>>>>>>>> demoProject.update(new NullProgressMonitor());
>>>>>>>>
>>>>>>>> // Finally we print the league and player names of
>>>>>>>> both
>>>>>>>> projects
>>>>>>>> System.out.println("\nLeague name in demoProject is
>>>>>>>> now: "
>>>>>>>> + league.getName());
>>>>>>>> System.out.println("\nLeague name in
>>>>>>>> demoProjectCopy is
>>>>>>>> now: " + leagueCopy.getName());
>>>>>>>> System.out.println("\nPlayer name in demoProject
>>>>>>>> is now:
>>>>>>>> " +
>>>>>>>> league.getPlayers().get(0).getName());
>>>>>>>> System.out.println("\nPlayer name in
>>>>>>>> demoProjectCopy is
>>>>>>>> now:
>>>>>>>> " + leagueCopy.getPlayers().get(0).getName());
>>>>>>>>
>>>>>>>> }
>>>>>>>> }
>>>>>>>>
>>>>>>>> private static void changeAndCommit(ESLocalProject
>>>>>>>> demoProject) {
>>>>>>>> League league = (League)
>>>>>>>> demoProject.getModelElements().get(0);
>>>>>>>> league.setName("Another name");
>>>>>>>> Player newPlayer =
>>>>>>>> BowlingFactory.eINSTANCE.createPlayer();
>>>>>>>> newPlayer.setName("Bill");
>>>>>>>> league.getPlayers().add(newPlayer);
>>>>>>>> try {
>>>>>>>> demoProject.commit(new ESSystemOutProgressMonitor());
>>>>>>>> } catch (ESException e) {
>>>>>>>> // TODO Auto-generated catch block
>>>>>>>> e.printStackTrace();
>>>>>>>> }
>>>>>>>> }
>>>>>>>>
>>>>>>>> public void stop() {
>>>>>>>> // do nothing
>>>>>>>> }
>>>>>>>> }
>>>>>>>
>>>>>>>
>>>>>>
>>>>>
>>>>>
>>>>
>>>
>>>
>>
>
>
Re: [EMFStore] Updates and commits with concurrent users [message #1338533 is a reply to message #1337168] Wed, 07 May 2014 08:59 Go to previous messageGo to next message
Maximilian Koegel is currently offline Maximilian KoegelFriend
Messages: 253
Registered: July 2009
Senior Member
OK, sounds like a plan ;).

Cheers,
Maximilian

Am 06.05.2014 21:00, schrieb Scott Dybiec:
>
> The primary advantage of combining the locking and versioning methods
> into one server side XML-RPC service is performance. Doing so reduces
> the number of round trips to the server and minimizes the time the lock
> is held. Since the current EMFStore implementation is based on the
> XML-RPC WebServer, a new TCP/IP connection is built and torn down for
> every request and this is somewhat expensive over a cell network.
>
> Since I really don't know the performance impact of implementing the
> composed lock/update & commit/unlock services vs. making individual
> calls for each one, it's hard to make a case one way or another.
>
> Let's let the data tell us what to do. Once the locking functions are
> built, I can performance test client-side and service-side
> implementations of the composed services and decide whether adding the
> additional methods to the core API is justified.
>
> Sound OK?
>
> Scott
>
> On 5/6/2014 3:59 AM, Maximilian Koegel wrote:
>> Hi Scott,
>>
>> what you propose is what I expected, although I would only offer two
>> methods:
>> acquireLock(String sessionId, String projectId, int acquireTimeout)
>> releaseLock(String sessionId, String projectId)
>> To my understanding the other methods could be offered on the client
>> side as convenience but are not required on server side.
>> What do you think?
>>
>> Cheers,
>> Maximilian
>>
>> P.S.: We also offer professional services in case you would like to
>> delegate the implementation to us ;).
>>
>> Am 02.05.2014 16:43, schrieb scott@xxxxxxxx:
>>> My testing shows that slow connections significantly increase the
>>> likelihood of competing commits, so my application will need this lock.
>>>
>>> I'll learn more about the EMFStore server internals and how best to
>>> implement this locking capability.
>>>
>>> Can you say a little more about how you envision the time-based lock to
>>> work? Is the timeout you suggest for acquiring the lock or for
>>> restricting the maximum time the lock can be held (an auto-unlock
>>> feature) -- or both?
>>>
>>> I'm thinking of four new methods in VersionSubInterfaceImpl something
>>> like this:
>>>
>>> acquireLock(String sessionId, String projectId, int acquireTimeout)
>>> releaseLock(String sessionId, String projectId)
>>> acquireLockAndGetChanges(...)
>>> createVersionAndReleaseLock(...)
>>>
>>> Any suggestions are welcome.
>>>
>>> $cott
>>>
>>> On 4/15/2014 5:51 AM, Maximilian Koegel wrote:
>>>> Yes, that is correct. EMFStore is intended for offline usage, like git
>>>> or svn, so typically you will not have many competing commits at the
>>>> same time. The problem with an atomic update and commit is that often
>>>> merging is done interactively so a lock would hold of all other
>>>> clients.
>>>> I would accept a time-baed lock for a project a additional API
>>>> though in
>>>> case you would propose a patch. We have no plans to implement something
>>>> like that ourself at his time.
>>>>
>>>> Cheers,
>>>> Maximilian
>>>>
>>>> Am 14.04.2014 15:05, schrieb scott@xxxxxxxx:
>>>>> OK, thanks for the quick response.
>>>>>
>>>>> Without a locking mechanism or a native two-way sync() function, it
>>>>> sounds like I'll need to repeatedly retry the update() and commit()
>>>>> until it succeeds or exceeds a retry limit. Hopefully that will
>>>>> work for
>>>>> the limited number of concurrent users I need to support.
>>>>>
>>>>> Any plans to implement a native sync() or lock mechanism?
>>>>>
>>>>> Thanks,
>>>>>
>>>>> Scott
>>>>>
>>>>>
>>>>> On 4/14/2014 7:24 AM, Maximilian Koegel wrote:
>>>>>> ESUpdateRequiredException means your client´s project is outdated and
>>>>>> needs an Update. You could keep updating but there is no guarantee it
>>>>>> will ever succeed to commit since other clients could keep pushing in
>>>>>> commits. To block other clients from making commits would require
>>>>>> additional API for locking, which is currently not implemented.
>>>>>> ChangeConflictException means your client´s project has conflicting
>>>>>> local changes with an update incoming from the server, which need
>>>>>> to be
>>>>>> resolved by merging. They could be auto-merged (conflictOccurred
>>>>>> method)
>>>>>> by just dropping one of the changes, if this is something that is
>>>>>> acceptable in your application.
>>>>>>
>>>>>> Cheers,
>>>>>> Maximilian
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>> Am 14.04.2014 01:56, schrieb scott@xxxxxxxx:
>>>>>>> Yes, your explanation sounds right and I get both
>>>>>>> ESUpdateRequiredException and ChangeConflictException depending
>>>>>>> where
>>>>>>> the other client update occurs.
>>>>>>>
>>>>>>> For example in the program I attached, if another client commits to
>>>>>>> the
>>>>>>> project during inspectChanges(), the UpdateController throws
>>>>>>> "ChangeConflictException: Conflict detected on
>>>>>>> update".
>>>>>>>
>>>>>>> Know any solutions to this problem?
>>>>>>>
>>>>>>> $cott
>>>>>>>
>>>>>>>
>>>>>>> On 4/11/2014 4:36 AM, Maximilian Koegel wrote:
>>>>>>>> Hi Scott,
>>>>>>>>
>>>>>>>> if I understand you correctly the "race conditions" are
>>>>>>>> conceptional
>>>>>>>> race conditions not real technical race conditions. They result
>>>>>>>> from
>>>>>>>> the
>>>>>>>> conceptional problem that clients can commit at any time but
>>>>>>>> only if
>>>>>>>> they have updated to the current head version they succeed with
>>>>>>>> their
>>>>>>>> commit. This is since otherwise a merge is required because of
>>>>>>>> concurrent changes. If any client commits in between the other
>>>>>>>> clients
>>>>>>>> update and commit attempt, the other client is forced to update
>>>>>>>> again.
>>>>>>>> The exceptions that you encounter are probably
>>>>>>>> ESUpdateRequiredException, right?
>>>>>>>>
>>>>>>>> Cheers,
>>>>>>>> Maximilian
>>>>>>>>
>>>>>>>>
>>>>>>>> Am 10.04.2014 17:11, schrieb scott@xxxxxxxx:
>>>>>>>>>
>>>>>>>>> My application needs a two-way sync capability in a multi-user
>>>>>>>>> (~10
>>>>>>>>> users) environment sharing a common project, but I'm not sure the
>>>>>>>>> best
>>>>>>>>> way to implement this. So far I naively implemented 'sync' as a
>>>>>>>>> combination of EMFStore's update() and commit() operations, and
>>>>>>>>> I'm
>>>>>>>>> struggling to avoid a race condition I've encountered during
>>>>>>>>> testing.
>>>>>>>>>
>>>>>>>>> Since update() and commit() are separate operations, there is
>>>>>>>>> room for
>>>>>>>>> another client (Client 2) to commit() in between Client 1's
>>>>>>>>> update() and
>>>>>>>>> commit() operations. update() and commit() are separate but
>>>>>>>>> sometimes
>>>>>>>>> dependent on one another and commits to the server can only be
>>>>>>>>> applied
>>>>>>>>> once the client and server have a common version of the model --
>>>>>>>>> which
>>>>>>>>> may require an update from the server. Depending on the
>>>>>>>>> ordering of
>>>>>>>>> these interweaved update() and commit() calls to the server from
>>>>>>>>> concurrent clients, I've found that exceptions can be thrown that
>>>>>>>>> are
>>>>>>>>> hard to recover from as there is no limit to the number times the
>>>>>>>>> exceptions could recur.
>>>>>>>>>
>>>>>>>>> Here's the scenario I've encountered using EMFStore's example
>>>>>>>>> merge
>>>>>>>>> application to demonstrate how these "race conditions" occur.
>>>>>>>>> Since
>>>>>>>>> any
>>>>>>>>> of the users can execute a commit() at any point, I've inserted a
>>>>>>>>> method
>>>>>>>>> call at various points to simulate the effects of other clients
>>>>>>>>> committing to the shared project
>>>>>>>>>
>>>>>>>>> changeAndCommit(ESLocalProject demoProject)
>>>>>>>>>
>>>>>>>>> to commit changes to the shared project on the server in
>>>>>>>>> between the
>>>>>>>>> update, commit and commit retry for the demoProjectCopy.
>>>>>>>>>
>>>>>>>>> Is there a coding pattern or example code I should adopt to
>>>>>>>>> implement a
>>>>>>>>> two-way sync capability? Is there any locking feature in EMFStore
>>>>>>>>> that I
>>>>>>>>> could use to eliminate the potential for the interweaving of
>>>>>>>>> update()
>>>>>>>>> and commit() calls to the server from concurrent clients? Or
>>>>>>>>> perhaps
>>>>>>>>> some other way to simulate an atomic sync function?
>>>>>>>>>
>>>>>>>>> Here's the modified merge example code demonstrating the race
>>>>>>>>> condition:
>>>>>>>>>
>>>>>>>>> package org.eclipse.emf.emfstore.example.merging;
>>>>>>>>>
>>>>>>>>> import java.io.File;
>>>>>>>>> import java.io.IOException;
>>>>>>>>> import java.util.List;
>>>>>>>>>
>>>>>>>>> import org.eclipse.core.runtime.IProgressMonitor;
>>>>>>>>> import org.eclipse.core.runtime.NullProgressMonitor;
>>>>>>>>> import org.eclipse.emf.emfstore.bowling.BowlingFactory;
>>>>>>>>> import org.eclipse.emf.emfstore.bowling.League;
>>>>>>>>> import org.eclipse.emf.emfstore.bowling.Player;
>>>>>>>>> import org.eclipse.emf.emfstore.client.ESLocalProject;
>>>>>>>>> import org.eclipse.emf.emfstore.client.ESServer;
>>>>>>>>> import org.eclipse.emf.emfstore.client.ESWorkspace;
>>>>>>>>> import org.eclipse.emf.emfstore.client.ESWorkspaceProvider;
>>>>>>>>> import org.eclipse.emf.emfstore.client.callbacks.ESUpdateCallback;
>>>>>>>>> import
>>>>>>>>> org.eclipse.emf.emfstore.client.exceptions.ESServerStartFailedException;
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> import org.eclipse.emf.emfstore.common.ESSystemOutProgressMonitor;
>>>>>>>>> import
>>>>>>>>> org.eclipse.emf.emfstore.common.model.ESModelElementIdToEObjectMapping;
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> import
>>>>>>>>> org.eclipse.emf.emfstore.internal.client.model.Configuration;
>>>>>>>>> import
>>>>>>>>> org.eclipse.emf.emfstore.internal.client.model.exceptions.ChangeConflictException;
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> import
>>>>>>>>> org.eclipse.emf.emfstore.internal.common.model.util.FileUtil;
>>>>>>>>> import
>>>>>>>>> org.eclipse.emf.emfstore.internal.server.ServerConfiguration;
>>>>>>>>> import org.eclipse.emf.emfstore.server.ESConflict;
>>>>>>>>> import org.eclipse.emf.emfstore.server.ESConflictSet;
>>>>>>>>> import org.eclipse.emf.emfstore.server.exceptions.ESException;
>>>>>>>>> import
>>>>>>>>> org.eclipse.emf.emfstore.server.exceptions.ESUpdateRequiredException;
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> import org.eclipse.emf.emfstore.server.model.ESChangePackage;
>>>>>>>>> import
>>>>>>>>> org.eclipse.emf.emfstore.server.model.versionspec.ESVersionSpec;
>>>>>>>>> import org.eclipse.equinox.app.IApplication;
>>>>>>>>> import org.eclipse.equinox.app.IApplicationContext;
>>>>>>>>>
>>>>>>>>> /**
>>>>>>>>> * An application that runs the demo.<br>
>>>>>>>>> * Run a client that shows the merging feature of the EMFstore
>>>>>>>>> * Please note: this is the programmatic way of merging
>>>>>>>>> * EMFStore also provides a default UI for merging
>>>>>>>>> * If there is a problem with the connection to the server
>>>>>>>>> * e.g. a network, a specific ESException will be thrown
>>>>>>>>> */
>>>>>>>>> public class RaceConditionTestApplication implements
>>>>>>>>> IApplication {
>>>>>>>>>
>>>>>>>>> /**
>>>>>>>>> * {@inheritDoc}
>>>>>>>>> */
>>>>>>>>> public Object start(IApplicationContext context) {
>>>>>>>>>
>>>>>>>>> try {
>>>>>>>>> cleanEmfstoreFolders();
>>>>>>>>>
>>>>>>>>> // Create a client representation for a local
>>>>>>>>> server and
>>>>>>>>> start a local server.
>>>>>>>>> ESServer localServer =
>>>>>>>>> ESServer.FACTORY.createAndStartLocalServer();
>>>>>>>>>
>>>>>>>>> /*
>>>>>>>>> * Reuse the client from the hello world
>>>>>>>>> example. It
>>>>>>>>> will
>>>>>>>>> clean up all local and remote projects and create
>>>>>>>>> * one project with some content on the server and
>>>>>>>>> two
>>>>>>>>> checked-out copies of the project on the client.
>>>>>>>>> */
>>>>>>>>>
>>>>>>>>> org.eclipse.emf.emfstore.example.helloworld.Application.runClient(localServer);
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> // We run our own client code to demonstrate
>>>>>>>>> merging
>>>>>>>>> now.
>>>>>>>>> runClient(localServer);
>>>>>>>>>
>>>>>>>>> } catch (ESServerStartFailedException e) {
>>>>>>>>> System.out.println("Server start failed!");
>>>>>>>>> e.printStackTrace();
>>>>>>>>> } catch (ESException e) {
>>>>>>>>> /*
>>>>>>>>> * If there is a problem with the connection to
>>>>>>>>> the
>>>>>>>>> server
>>>>>>>>> * e.g. a network, a specific EMFStoreException
>>>>>>>>> will be
>>>>>>>>> thrown
>>>>>>>>> */
>>>>>>>>> System.out.println("Connection to Server
>>>>>>>>> failed!");
>>>>>>>>> e.printStackTrace();
>>>>>>>>> }
>>>>>>>>> return IApplication.EXIT_OK;
>>>>>>>>> }
>>>>>>>>>
>>>>>>>>> @SuppressWarnings("restriction")
>>>>>>>>> private static void cleanEmfstoreFolders() {
>>>>>>>>> try {
>>>>>>>>> String clientWorkspaceDirectoryString =
>>>>>>>>> Configuration.getFileInfo().getWorkspaceDirectory();
>>>>>>>>> File clientWorkspaceDirectory = new
>>>>>>>>> File(clientWorkspaceDirectoryString);
>>>>>>>>> System.out.println("Deleting client workspace
>>>>>>>>> directory " +
>>>>>>>>> clientWorkspaceDirectory.getAbsolutePath());
>>>>>>>>> FileUtil.deleteDirectory(clientWorkspaceDirectory,
>>>>>>>>> true);
>>>>>>>>>
>>>>>>>>> String serverWorkspaceDirectoryString =
>>>>>>>>> ServerConfiguration.getLocationProvider().getWorkspaceDirectory();
>>>>>>>>> File serverWorkspaceDirectory = new
>>>>>>>>> File(serverWorkspaceDirectoryString);
>>>>>>>>> System.out.println("Deleting server workspace
>>>>>>>>> directory " +
>>>>>>>>> serverWorkspaceDirectory.getAbsolutePath());
>>>>>>>>> FileUtil.deleteDirectory(serverWorkspaceDirectory,
>>>>>>>>> true);
>>>>>>>>> } catch (IOException e) {
>>>>>>>>> throw new RuntimeException(e);
>>>>>>>>> }
>>>>>>>>> }
>>>>>>>>>
>>>>>>>>> public static void runClient(ESServer server) throws
>>>>>>>>> ESException {
>>>>>>>>> System.out.println("Client starting...");
>>>>>>>>>
>>>>>>>>> ESWorkspace workspace =
>>>>>>>>> ESWorkspaceProvider.INSTANCE.getWorkspace();
>>>>>>>>> final ESLocalProject demoProject =
>>>>>>>>> workspace.getLocalProjects().get(0);
>>>>>>>>> League league = (League)
>>>>>>>>> demoProject.getModelElements().get(0);
>>>>>>>>> final ESLocalProject demoProjectCopy =
>>>>>>>>> workspace.getLocalProjects().get(1);
>>>>>>>>> League leagueCopy = (League)
>>>>>>>>> demoProjectCopy.getModelElements().get(0);
>>>>>>>>>
>>>>>>>>> // Change the name of the league in project 1,add a
>>>>>>>>> new
>>>>>>>>> player
>>>>>>>>> and commit the change
>>>>>>>>> league.setName("Euro-League");
>>>>>>>>> Player newPlayer =
>>>>>>>>> BowlingFactory.eINSTANCE.createPlayer();
>>>>>>>>> newPlayer.setName("Eugene");
>>>>>>>>> league.getPlayers().add(newPlayer);
>>>>>>>>>
>>>>>>>>> demoProject.commit(new ESSystemOutProgressMonitor());
>>>>>>>>>
>>>>>>>>> /*
>>>>>>>>> * Changing the name again value without calling
>>>>>>>>> update() on
>>>>>>>>> the
>>>>>>>>> copy first will cause a conflict on commit.
>>>>>>>>> * We also add one change which is non-conflicting,
>>>>>>>>> setting the
>>>>>>>>> name of the first player.
>>>>>>>>> */
>>>>>>>>> leagueCopy.setName("EU-League");
>>>>>>>>> leagueCopy.getPlayers().get(0).setName("Johannes");
>>>>>>>>>
>>>>>>>>> try {
>>>>>>>>> demoProjectCopy.commit(new
>>>>>>>>> ESSystemOutProgressMonitor());
>>>>>>>>> } catch (ESUpdateRequiredException e) {
>>>>>>>>> // The commit failed since the other
>>>>>>>>> demoProject was
>>>>>>>>> committed first and therefore demoProjectCopy needs an
>>>>>>>>> // update
>>>>>>>>> System.out.println("\nCommit of demoProjectCopy
>>>>>>>>> failed.");
>>>>>>>>>
>>>>>>>>> // We run update in demoProjectCopy with an
>>>>>>>>> UpdateCallback
>>>>>>>>> to handle conflicts
>>>>>>>>> System.out.println("\nUpdate of demoProjectCopy
>>>>>>>>> with
>>>>>>>>> conflict resolver...");
>>>>>>>>>
>>>>>>>>> demoProjectCopy.update(ESVersionSpec.FACTORY.createHEAD(),
>>>>>>>>> new ESUpdateCallback() {
>>>>>>>>>
>>>>>>>>> boolean changeMade = false;
>>>>>>>>>
>>>>>>>>> public void noChangesOnServer() {
>>>>>>>>> /*
>>>>>>>>> * do nothing if there are no changes
>>>>>>>>> on the
>>>>>>>>> server
>>>>>>>>> (in this example we know
>>>>>>>>> * there are changes anyway)
>>>>>>>>> */
>>>>>>>>> }
>>>>>>>>>
>>>>>>>>> public boolean inspectChanges(ESLocalProject
>>>>>>>>> project,
>>>>>>>>> List<ESChangePackage> changes,
>>>>>>>>> ESModelElementIdToEObjectMapping
>>>>>>>>> idToEObjectMapping) {
>>>>>>>>>
>>>>>>>>> /*
>>>>>>>>> * If another client commits to the
>>>>>>>>> project
>>>>>>>>> during
>>>>>>>>> inspectChanges(), the UpdateController throws
>>>>>>>>> * "ChangeConflictException: Conflict
>>>>>>>>> detected on
>>>>>>>>> update"
>>>>>>>>> */
>>>>>>>>> if (false) {
>>>>>>>>> changeAndCommit(demoProject);
>>>>>>>>> }
>>>>>>>>> // allow update to proceed, here we could
>>>>>>>>> also add
>>>>>>>>> some UI
>>>>>>>>> return true;
>>>>>>>>> }
>>>>>>>>>
>>>>>>>>> public boolean conflictOccurred(ESConflictSet
>>>>>>>>> changeConflictSet, IProgressMonitor monitor) {
>>>>>>>>> /*
>>>>>>>>> * If another client commits to the
>>>>>>>>> project
>>>>>>>>> during
>>>>>>>>> inspectChanges(), the UpdateController throws
>>>>>>>>> * "ChangeConflictException: Conflict
>>>>>>>>> detected on
>>>>>>>>> update"
>>>>>>>>> */
>>>>>>>>> if (!changeMade && false) {
>>>>>>>>> changeAndCommit(demoProject);
>>>>>>>>> changeMade = true;
>>>>>>>>> }
>>>>>>>>> /*
>>>>>>>>> * One or more conflicts have occured,
>>>>>>>>> they are
>>>>>>>>> delivered in a change conflict set
>>>>>>>>> * We know there is only one conflict
>>>>>>>>> so we
>>>>>>>>> grab it
>>>>>>>>> */
>>>>>>>>> ESConflict conflict =
>>>>>>>>> changeConflictSet.getConflicts().iterator().next();
>>>>>>>>>
>>>>>>>>> /*
>>>>>>>>> * We resolve the conflict by accepting
>>>>>>>>> all
>>>>>>>>> of the
>>>>>>>>> conflicting local operations and rejecting all of
>>>>>>>>> * the remote operations. This means
>>>>>>>>> that we
>>>>>>>>> revert
>>>>>>>>> the league name change of demoProject and accept
>>>>>>>>> * the league name change of
>>>>>>>>> demoProjectCopy.
>>>>>>>>> The
>>>>>>>>> player name change in demoProject is accepted also
>>>>>>>>> * since it was not conflicting with any
>>>>>>>>> other
>>>>>>>>> change.
>>>>>>>>> */
>>>>>>>>>
>>>>>>>>> conflict.resolveConflict(conflict.getLocalOperations(),
>>>>>>>>> conflict.getRemoteOperations());
>>>>>>>>> /*
>>>>>>>>> * Finally we claim to have resolved all
>>>>>>>>> conflicts
>>>>>>>>> so update will try to proceed.
>>>>>>>>> */
>>>>>>>>> return true;
>>>>>>>>> }
>>>>>>>>> }, new ESSystemOutProgressMonitor());
>>>>>>>>>
>>>>>>>>> /*
>>>>>>>>> * Commits from other clients at this point
>>>>>>>>> results in
>>>>>>>>> another
>>>>>>>>> * "ESUpdateRequiredException: BaseVersion
>>>>>>>>> outdated,
>>>>>>>>> please
>>>>>>>>> update before commit." exception being thrown.
>>>>>>>>> */
>>>>>>>>> changeAndCommit(demoProject);
>>>>>>>>>
>>>>>>>>> // commit merge result in project 2
>>>>>>>>> System.out.println("\nCommit of merge result of
>>>>>>>>> demoProjectCopy");
>>>>>>>>> demoProjectCopy.commit(new
>>>>>>>>> ESSystemOutProgressMonitor());
>>>>>>>>>
>>>>>>>>> // After having merged the two projects update
>>>>>>>>> local
>>>>>>>>> project 1
>>>>>>>>> System.out.println("\nUpdate of demoProject");
>>>>>>>>> demoProject.update(new NullProgressMonitor());
>>>>>>>>>
>>>>>>>>> // Finally we print the league and player names of
>>>>>>>>> both
>>>>>>>>> projects
>>>>>>>>> System.out.println("\nLeague name in
>>>>>>>>> demoProject is
>>>>>>>>> now: "
>>>>>>>>> + league.getName());
>>>>>>>>> System.out.println("\nLeague name in
>>>>>>>>> demoProjectCopy is
>>>>>>>>> now: " + leagueCopy.getName());
>>>>>>>>> System.out.println("\nPlayer name in demoProject
>>>>>>>>> is now:
>>>>>>>>> " +
>>>>>>>>> league.getPlayers().get(0).getName());
>>>>>>>>> System.out.println("\nPlayer name in
>>>>>>>>> demoProjectCopy is
>>>>>>>>> now:
>>>>>>>>> " + leagueCopy.getPlayers().get(0).getName());
>>>>>>>>>
>>>>>>>>> }
>>>>>>>>> }
>>>>>>>>>
>>>>>>>>> private static void changeAndCommit(ESLocalProject
>>>>>>>>> demoProject) {
>>>>>>>>> League league = (League)
>>>>>>>>> demoProject.getModelElements().get(0);
>>>>>>>>> league.setName("Another name");
>>>>>>>>> Player newPlayer =
>>>>>>>>> BowlingFactory.eINSTANCE.createPlayer();
>>>>>>>>> newPlayer.setName("Bill");
>>>>>>>>> league.getPlayers().add(newPlayer);
>>>>>>>>> try {
>>>>>>>>> demoProject.commit(new
>>>>>>>>> ESSystemOutProgressMonitor());
>>>>>>>>> } catch (ESException e) {
>>>>>>>>> // TODO Auto-generated catch block
>>>>>>>>> e.printStackTrace();
>>>>>>>>> }
>>>>>>>>> }
>>>>>>>>>
>>>>>>>>> public void stop() {
>>>>>>>>> // do nothing
>>>>>>>>> }
>>>>>>>>> }
>>>>>>>>
>>>>>>>>
>>>>>>>
>>>>>>
>>>>>>
>>>>>
>>>>
>>>>
>>>
>>
>>
>


--
Maximilian Kögel

Get Professional Eclipse Support: http://eclipsesource.com/munich
Re: [EMFStore] Updates and commits with concurrent users [message #1403982 is a reply to message #1338533] Mon, 28 July 2014 22:14 Go to previous message
Roza Ghamari is currently offline Roza GhamariFriend
Messages: 82
Registered: January 2013
Member
Hi Scott,

Have you implemented the locking methods by any chance? It would be great if you can share your implementations with us.
Previous Topic:[EMFStore] Blocker : Commit of EContainment is throwing NPE
Next Topic:[EMFForms] Standalone rendering forms
Goto Forum:
  


Current Time: Thu Mar 28 11:04:59 GMT 2024

Powered by FUDForum. Page generated in 0.05129 seconds
.:: Contact :: Home ::.

Powered by: FUDforum 3.0.2.
Copyright ©2001-2010 FUDforum Bulletin Board Software

Back to the top