Short introduction:
I have a [physics] simulation framework which used to be a single-user desktop version. The framework serves as a toolkit to enable e.g. teachers to build various kinds of simulation setups without in-depth knowledge of Java programming and/or specific maths. Eventually the idea came up to apply a client-server paradigm to the framework to allow multiple clients to collaborate while working with the same simulation (= to synchronize the simulation across all clients).
Some additional technical facts:
The framework/simulations are designed based on an MVC pattern.
If a client performs changes to the simulation - for example via the Swing GUI by moving a slider or by mouse-dragging simulation elements - these changes have to be authorized by the server before they get applied to the simulation (+ the server has to take care to distribute the changes to all other clients, which also have to apply them).
Authorization itself is very simple and basically only decides upon timestamps whether or not a change should be accepted (to avoid problems caused by clients with different latencies causing changes to the same thing at the [almost] same time).
The problem respectively the question:
I'm wondering now what's the most elegant way to detect (and intercept) user inputs (to pass them to the underlying messaging system responsible for the client-server communication)? Is there some kind of pattern or best practice which is commonly used for the described scenario?
One of my main concerns is to avoid introducing new constraints which have to be taken into account by the persons using the framework to build new content (=simulations). In this sense I'd like to keep existing simulations working with as few changes required as possible.
One solution I thought could work:
I thought about introducing a new interface like:
public interface Synchronizable {
public boolean appliesChanges();
}
The constraint in this case would be that change listeners of any type would have to additionally implement this interface if they want the change events they are listening for to be synchronized. By doing so the underlying framework could replace all objects implementing Synchronizable with proxy objects in charge of validating a change [event] with the server (and upon success forwarding the event to the real change listener).
The idea behind the 'appliesChanges' method would be that not all calls to a change listener really result in a change which needs synchronization. For example a Swing JSlider might generate events whenever the knob gets moved, but a concrete implementation of a change listener might only apply a real c开发者_如何学Gohange once the knob is released (that is the 'value is not adjusting' anymore). Change events occurring in-between wouldn't need to be sent to the server since they have no effect anyway. This approach would neither be convenient nor particularly pretty but I couldn't think of any other possibility to solve the issue in a different way?!
Besides the problem that end users would have to explicitly think about which events they'd like to have synchronized (and therefore declare and implement the mentioned interface for all of the particular listeners), one remaining problem would be how to automatically find out which specific method is responsible for handling events of an arbitrary type (to avoid having to implement concrete proxies for every possible change listener).
Example outlining this issue:
public interface SynchronizedChangeListener extends ChangeListener, Synchronizable {}
public interface SynchronizedPropertyChangeListener extends PropertyChangeListener, Synchronizable {}
public static void main(String[] args) {
SynchronizedChangeListener scl = new SynchronizedChangeListener() {
public void stateChanged(ChangeEvent e) {
System.out.println("Hello world - SynchronizedChangeListener");
}
public boolean appliesChanges() {
return true;
}
};
SynchronizedPropertyChangeListener spcl = new SynchronizedPropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
System.out.println("Hello world - SynchronizedPropertyChangeListener");
}
public boolean appliesChanges() {
return true;
}
};
}
How would the proxy listener know that for PropertyChangeEvents it has to call the 'propertyChange' method whereas for the ChangeEvents it has to call the 'stateChanged' method? Is reflection capable of solving this issue?
Looking forward to your inputs - I'd be happy about any thought's you may have or any re-directions to literature dealing with this topic.
As I see it you have a User UI where a user can provide some input. You have another Simulation UI which is running a physics simulation. Input from the User UI should be fed to the other only after a server authorises it.
So far so good?
Now your Simulation UI should wait for events from your server ultimately. I was thinking of something like this:
- Prepare an XML which contains the changes the user wants from the user ui.
- Implement a simple servlet (or struts) on a tomcat server to parse this XML and respond back to the user who asked for it with the authorisation. The response can be in XML as well.
- Feed this authorisation back to the simulation UI through a queue. The simulation UI should be listening on this queue for any events put onto it.
- This takes care of a single user scenario. For multiple users receiving events from the server, the easiest way of doing this is polling. Implement a timer on a level before the simulation UI which will be poll the server. The server can respond back with the latest changes. Now feed this into the queue of the simulation UI.
I'm not entirely sure I understand your question, but at its base it seems to be a concurrency issue about allow multiple clients to change the state of a service at potentially the same time.
As I understand it, you have a client which allows the user to view the state of a system (your simulation) and modify it. On the client side, you have some event-based MVC, where the listener is waiting for changes from the UI, and then passing the changes along to a client which notifies the server. In a very rudimentary sense, like this:
Today:
-------- ------------------ ----------
| UI | - change event -> | Event Listener | - set value -> | Server |
-------- ------------------ ----------
If I understand your proposed solution correctly, you want to intercept these events and first ask the server if anyone has recently changed the state before you attempt to make the changes.
Proposed:
-------- ------------------ ------------------ ----------
| UI | - change event -> | Proxy Listener | - event -> | Event Listener | - set value -> | Server |
-------- ------------------ ------------------ ----------
|
timestamp
|
v
----------
| Server |
----------
In my opinion, this is not an ideal solution for various reasons. First, you are talking to the server twice. Secondly, you will need to figure out how to make proxies for the various types of event handlers which exist, something you alluded to in your "how do I know which method to call" section. This will require either a bunch of different proxy listener implementations or some very messy reflective logic. (You could probably accomplish this using dynamic proxies.) Lastly, you need to rely on the proxy listener being added every time, so concurrent safety is not the default.
I think a more general solution which can be applied to your current use cases and any future extensions would be look at making the set value call to the server safe. That is the Server should detect when the Event Listener tries to make a change on a value that is up to date (aka: value has gone "stale") and, in that case, refuse to make that change. In this case, there are two smaller problems to solve: how to detect the change on a stale state, and secondly how to tell the client that the change was not performed.
For the first problem, you may wish to implement some form of optimistic locking. Similar to your proposed solution, you can use timestamps for this. For example, when the UI renders, it will get the value from the server. The server can pass back the value and also the timestamp of when that value was last modified. When the user makes a change, the client passes back the new value to the server and the original last modified timestamp. (ie: "Server, set foo to 10 and the last time someone changed foo was yesterday.") The server then knows if your change is on the most recent value, and if not, can refuse the change.
If you don't have an ability to store a last modified timestamp with every value which may be changed, you can accomplish the same thing with a hash. When the server returns the value to the client, calculate and return a hash with the value. When the server receives an update request, the client will pass back the hash and the server can re-calculate the hash from the value it has. If they match, then the client's change is on the most recent value.
(Terminology alert: whether you use a timestamp or a hash, this value serves as the "mutex.")
You don't provide a whole lot of details about how the client and server interact, so I can only make guesses as to the interface there, but if the server has the ability to track clients in server-side sessions, you could make the work of storing and providing the appropriate mutex value entirely on the server side. In this case, the server keeps track of what values it has provided to a particular client for a given session and what were the mutex values. When the client comes back with an update request on the same session, the server can look up the mutex for that value and perform the appropriate action without the client even aware that the server is doing so. In this case, you can enforce concurrent-safe code without changing your client at all from how it looks today.
As to the second problem, of how to notify the client that a change was rejected because it was stale, this should be relatively easy depending on how your client and server interact. If you are using some sort of SOAP or RPC which allows for remote exceptions, I would encourage you to throw back something similar to Java's ConcurrentModificationException. In that case, all you have to do is in the View, catch the exception and handle it appropriately. That likely means calling the server again to get the most up-to-date value and probably notifying the user that their requested update was not accepted because the state of the simulation changed from underneath them.
Synchronisation requires consensus. If you are able to tolerate some deviations (read discontinuities) in your simulations, you could allow clients to be forced to accept values set at another station when the updates are propagated. If there is a simulation-local clock, then the updates can be shipped with this value to prevent the change from potentially resulting in different results.
The other answers consider the problems in multiple communication and the potential to synchronise using mutexes.
One approach is to lock resources when they are touched (possibly with some kind of instructor preference or override). Publish the lock to prevent local modification and then distribute the updates until the lock is released.
You can avoid the lock if you have asymmetric clients where only designated users can control the variables making the lock implicit.
If you use a multicast (either for real or by means of pub-sub) distribution of all client variables, then this can form part of a scheduled state update that does not require specific support (beyond your 'value is not adjusting' optimisation).
The real decision is whether you intend to permit multi-master updates, or to limit changes from a single source. This will fundamentally determine the algorithm, the protocol and implementation.
精彩评论