Skip to main content



      Home
Home » Modeling » EMF » Copy On Write EList
Copy On Write EList [message #704108] Thu, 28 July 2011 05:30 Go to next message
Eclipse UserFriend
Hi all,

I need to add concurrency protection to the EList. I know I should be using the transactional model but it is not possible to do so in this project. The threads are running within the model objects itself.

Therefore, I would like to give it a go to implement a CopyOnWriteBasicEList. This should not be too complicated. Now, knowing which subclasses of BasicEList i also need to implement as CopyOnWrite versions and what to take into account on the JET templates to instanteate the right List implementation, that's where it starts getting tricky, at least for me.

So, any guidelines or advise on this will be wellcome!

Cheers
Javi



Re: Copy On Write EList [message #704317 is a reply to message #704108] Thu, 28 July 2011 09:43 Go to previous messageGo to next message
Eclipse UserFriend
Javi,

Comments below.

On 28/07/2011 2:30 AM, Xavipen wrote:
> Hi all,
>
> I need to add concurrency protection to the EList. I know I should be
> using the transactional model but it is not possible to do so in this
> project. The threads are running within the model objects itself.
It's generally very hard (impossible is probably the better word) to
achieve proper concurrency protection at a fine grained level without a
high level policy about how threads coordinate reads and writes of a model.
>
> Therefore, I would like to give it a go to implement a
> CopyOnWriteBasicEList.
And what about single valued features? Of course the list that
implement a multi-valued feature are intimately tied to the object that
owns them, so I don't see the real possibility for this to be supported.
> This should not be too complicated.
I think you'll find it is.
> Now, knowing which subclasses of BasicEList i also need to implement
> as CopyOnWrite versions and what to take into account on the JET
> templates to instanteate the right List implementation, that's where
> it starts getting tricky, at least for me.
So you do want to copy a multi-valued feature's list...
> So, any guidelines or advise on this will be wellcome!
It's just not going to work. The original uncopied owning object will
refer to which version of the list? The uncopied one? Who will ever
see the copied version given that no object refers to it?

You're better off looking into CDO.
>
> Cheers
> Javi
>
>
>
>
Re: Copy On Write EList [message #704393 is a reply to message #704317] Thu, 28 July 2011 11:14 Go to previous messageGo to next message
Eclipse UserFriend
Quote:

Comments below.

On 28/07/2011 2:30 AM, Xavipen wrote:
> Hi all,
>
> I need to add concurrency protection to the EList. I know I should be
> using the transactional model but it is not possible to do so in this
> project. The threads are running within the model objects itself.
It's generally very hard (impossible is probably the better word) to
achieve proper concurrency protection at a fine grained level without a
high level policy about how threads coordinate reads and writes of a model.



In this case the model also reflects beheiviour. I am modelling a protocol chain where the beheiviour will change according to how the model is configured. Each main block (Which is also an EObject) will have its own thread.

I'm very aware this is a debiation of the objectives of EMF Model. However by modifiing the templates i can generate the concurrency for EAttributes and EReferneces when they are a single reference. (EReferencse actual object contain is then protected for a high level concurrency policy base in Actors)

However i have not found it for EList.

Quote:
>
> Therefore, I would like to give it a go to implement a
> CopyOnWriteBasicEList.
And what about single valued features? Of course the list that
implement a multi-valued feature are intimately tied to the object that
owns them, so I don't see the real possibility for this to be supported.


I'm sorry. I am not sure what do you mean with single valued features

Quote:
> This should not be too complicated.
I think you'll find it is.


I was going to take and look the implementation of java.util.concurrent.CopyOnWriteArrayList<E> from Java. AS this should be enough
protection for what i needed. Quoting Java doc: That implementation may be more efficient than alternatives when traversal operations vastly outnumber mutations, and is useful when you cannot or don't want to synchronize traversals, yet need to preclude interference among concurrent threads.

Quote:
> Now, knowing which subclasses of BasicEList i also need to implement
> as CopyOnWrite versions and what to take into account on the JET
> templates to instanteate the right List implementation, that's where
> it starts getting tricky, at least for me.
So you do want to copy a multi-valued feature's list...
> So, any guidelines or advise on this will be wellcome!
It's just not going to work. The original uncopied owning object will
refer to which version of the list? The uncopied one? Who will ever
see the copied version given that no object refers to it?


Again, not sure what do you mean by: So you do want to copy a multi-valued feature's list...

I have to look the exact implementation of the CopyonWrite, but basically what it will do is create a new array when a write operation is done. The iterators already created will be referencing the old array. The new ones will refer to the new array.
Read operations will synchronize getting the reference to the array containing the data. But all this is how the array is internally handle. Externally to EMF should be no difference. Shouldn't it?

Quote:

You're better off looking into CDO.
>
> Cheers
> Javi
>
>
>
>


Re: Copy On Write EList [message #704397 is a reply to message #704393] Thu, 28 July 2011 11:21 Go to previous messageGo to next message
Eclipse UserFriend
Well why don't you simply return a wrapper list which also has this high
level thread protection?

Tom

Am 28.07.11 17:14, schrieb Xavipen:
> Quote:
>> Comments below.
>>
>> On 28/07/2011 2:30 AM, Xavipen wrote:
>> > Hi all,
>> >
>> > I need to add concurrency protection to the EList. I know I should
>> be > using the transactional model but it is not possible to do so in
>> this > project. The threads are running within the model objects itself.
>> It's generally very hard (impossible is probably the better word) to
>> achieve proper concurrency protection at a fine grained level without
>> a high level policy about how threads coordinate reads and writes of a
>> model.
>
>
> In this case the model also reflects beheiviour. I am modelling a
> protocol chain where the beheiviour will change according to how the
> model is configured. Each main block (Which is also an EObject) will
> have its own thread.
>
> I'm very aware this is a debiation of the objectives of EMF Model.
> However by modifiing the templates i can generate the concurrency for
> EAttributes and EReferneces when they are a single reference.
> (EReferencse actual object contain is then protected for a high level
> concurrency policy base in Actors)
>
> However i have not found it for EList.
>
> Quote:
>> >
>> > Therefore, I would like to give it a go to implement a >
>> CopyOnWriteBasicEList. And what about single valued features? Of
>> course the list that implement a multi-valued feature are intimately
>> tied to the object that owns them, so I don't see the real possibility
>> for this to be supported.
>
>
> I'm sorry. I am not sure what do you mean with single valued features
>
> Quote:
>> > This should not be too complicated. I think you'll find it is.
>
>
> I was going to take and look the implementation of
> java.util.concurrent.CopyOnWriteArrayList<E> from Java. AS this should
> be enough protection for what i needed. Quoting Java doc: That
> implementation may be more efficient than alternatives when traversal
> operations vastly outnumber mutations, and is useful when you cannot or
> don't want to synchronize traversals, yet need to preclude interference
> among concurrent threads.
>
> Quote:
>> > Now, knowing which subclasses of BasicEList i also need to implement
>> > as CopyOnWrite versions and what to take into account on the JET >
>> templates to instanteate the right List implementation, that's where >
>> it starts getting tricky, at least for me.
>> So you do want to copy a multi-valued feature's list...
>> > So, any guidelines or advise on this will be wellcome!
>> It's just not going to work. The original uncopied owning object
>> will refer to which version of the list? The uncopied one? Who will
>> ever see the copied version given that no object refers to it?
>
>
> Again, not sure what do you mean by: So you do want to copy a
> multi-valued feature's list...
>
> I have to look the exact implementation of the CopyonWrite, but
> basically what it will do is create a new array when a write operation
> is done. The iterators already created will be referencing the old
> array. The new ones will refer to the new array.
> Read operations will synchronize getting the reference to the array
> containing the data. But all this is how the array is internally handle.
> Externally to EMF should be no difference. Shouldn't it?
> Quote:
>> You're better off looking into CDO.
>> >
>> > Cheers
>> > Javi
>> >
>> >
>> >
>> >
>
>
>
Re: Copy On Write EList [message #704412 is a reply to message #704393] Thu, 28 July 2011 11:39 Go to previous messageGo to next message
Eclipse UserFriend
Comments below.

On 28/07/2011 8:14 AM, Xavipen wrote:
> Quote:
>> Comments below.
>>
>> On 28/07/2011 2:30 AM, Xavipen wrote:
>> > Hi all,
>> >
>> > I need to add concurrency protection to the EList. I know I should
>> be > using the transactional model but it is not possible to do so in
>> this > project. The threads are running within the model objects itself.
>> It's generally very hard (impossible is probably the better word) to
>> achieve proper concurrency protection at a fine grained level without
>> a high level policy about how threads coordinate reads and writes of
>> a model.
>
>
> In this case the model also reflects beheiviour. I am modelling a
> protocol chain where the beheiviour will change according to how the
> model is configured. Each main block (Which is also an EObject) will
> have its own thread.
This makes it sound like there isn't multi-threaded access to these but
rather each thread accesses a different thing...
>
> I'm very aware this is a debiation of the objectives of EMF Model.
> However by modifiing the templates i can generate the concurrency for
> EAttributes and EReferneces when they are a single reference.
> (EReferencse actual object contain is then protected for a high level
> concurrency policy base in Actors)
I have no idea what those modifications would do... You're just talking
about adding synchronize?
>
> However i have not found it for EList.
>
> Quote:
>> >
>> > Therefore, I would like to give it a go to implement a >
>> CopyOnWriteBasicEList. And what about single valued features? Of
>> course the list that implement a multi-valued feature are intimately
>> tied to the object that owns them, so I don't see the real
>> possibility for this to be supported.
>
>
> I'm sorry. I am not sure what do you mean with single valued features
You just said above "single reference"; that's what I mean.
>
> Quote:
>> > This should not be too complicated. I think you'll find it is.
>
>
> I was going to take and look the implementation of
> java.util.concurrent.CopyOnWriteArrayList<E> from Java. AS this should
> be enough protection for what i needed.
It's a bit similar to what we do with BasicNotifierImpl.EAdapterList but
here the guy iterating over the list is basically given the array itself
and then that array will never be changed again.
> Quoting Java doc: That implementation may be more efficient than
> alternatives when traversal operations vastly outnumber mutations, and
> is useful when you cannot or don't want to synchronize traversals, yet
> need to preclude interference among concurrent threads.
>
> Quote:
>> > Now, knowing which subclasses of BasicEList i also need to
>> implement > as CopyOnWrite versions and what to take into account on
>> the JET > templates to instanteate the right List implementation,
>> that's where > it starts getting tricky, at least for me.
>> So you do want to copy a multi-valued feature's list...
>> > So, any guidelines or advise on this will be wellcome!
>> It's just not going to work. The original uncopied owning object
>> will refer to which version of the list? The uncopied one? Who will
>> ever see the copied version given that no object refers to it?
>
>
> Again, not sure what do you mean by: So you do want to copy a
> multi-valued feature's list...
You're talking about modify on write for lists and above you said "I've
not found it for EList; that's what I mean.
>
> I have to look the exact implementation of the CopyonWrite,
You're aware that copying JDK implementation details is a copyright/IP
issue?
> but basically what it will do is create a new array when a write
> operation is done. The iterators already created will be referencing
> the old array. The new ones will refer to the new array.
> Read operations will synchronize getting the reference to the array
> containing the data. But all this is how the array is internally
> handle. Externally to EMF should be no difference. Shouldn't it?
It's impossible for me to understand if your application will behave
consistently if your readers don't see the full state of the model
that's changing underneath. Most applications will not work well under
such circumstances because they'll do something at the end of all that
reading that might well not be meaningful if the model isn't really in
the expected state anymore. Anything that was read might have been
removed and there might be all kinds of objects present that weren't
considered...
> Quote:
>> You're better off looking into CDO.
>> >
>> > Cheers
>> > Javi
>> >
>> >
>> >
>> >
>
>
>
Re: Copy On Write EList [message #704523 is a reply to message #704412] Thu, 28 July 2011 14:39 Go to previous messageGo to next message
Eclipse UserFriend
Hi Ed,

I see the room for undesire/dangerous side effects and the need for a synchronization at a higher level, however for certain cases like list of specific listeners, this would be more than enough. For those cases. Yes, AdapterEList is along the lines of what i had in mind, but with a slight modification as follows: When add, remove, set or similar operations are called. A new array is created allways. (this bit of code of creating the new array will be synchronize with a lock).

Operations like get, indexOf, lastIndexOf, contains, etc... will no longer access data[] directly as they currently do, but in the first line of the method(or where appropiated) they will call data() (this method is already there) The method dta() will be also synchronize with a lock and return then the data[] as it is at that point in time.

If i have follow the hierarchy right this means that ECoreEList, EObjectEList EContainmendObjectEList.. will also need to take out their references to data[]. For now on when traversing the list fist it will be necesary to get a reference to the data[] via data().

Then JET templates should declared the new version of the EObjectList or EContainmentList when having a EReference to Many ( or a multi-valued feature List, is this the right thing to call it?)

Again, thanks for your comments and looking forward the next ones Smile
Javi
Re: Copy On Write EList [message #704530 is a reply to message #704397] Thu, 28 July 2011 14:56 Go to previous messageGo to next message
Eclipse UserFriend
Quote:
Well why don't you simply return a wrapper list which also has this high
level thread protection?

Tom


Hi Tom,

This is kind of what i was doing up to now. I will define on the interface the methods i needed to access the EList and then I model the class implementing the interface which would have a EList modelled and then i implement this thread protection myself and i was trying to simplify this somehow.

But now that i read your post for a second time, did you mean something like:

MyThreadProtectedElist implement EList{

public MyThreadProtectedEList ( EList list, Lock highlevellock){
internalList = list;
internalLock = lock;
}

// implement the synchronized calls to the internal list...

}


Humm, I did not though of that but I guess that could be a good alternative, unless I am missing something.

Cheers,
Javi
Re: Copy On Write EList [message #705220 is a reply to message #704523] Fri, 29 July 2011 10:59 Go to previous messageGo to next message
Eclipse UserFriend
Javi,

Comments below.

On 28/07/2011 11:39 AM, Xavipen wrote:
> Hi Ed,
>
> I see the room for undesire/dangerous side effects and the need for a
> synchronization at a higher level, however for certain cases like list
> of specific listeners, this would be more than enough. For those
> cases. Yes, AdapterEList is along the lines of what i had in mind, but
> with a slight modification as follows: When add, remove, set or
> similar operations are called. A new array is created allways. (this
> bit of code of creating the new array will be synchronize with a lock).
I see.
>
> Operations like get, indexOf, lastIndexOf, contains, etc... will no
> longer access data[] directly as they currently do, but in the first
> line of the method(or where appropiated) they will call data() (this
> method is already there) The method dta() will be also synchronize
> with a lock and return then the data[] as it is at that point in time.
Of course as soon as you do locking, you can have dead lock...
>
> If i have follow the hierarchy right this means that ECoreEList,
> EObjectEList EContainmendObjectEList.. will also need to take out
> their references to data[]. For now on when traversing the list fist
> it will be necesary to get a reference to the data[] via data().
I expect you'd be far better to look into using DelegatingEcoreEList:
multi-valued features should return something that implements
InternalEList and EStructuralFeature.Setting. This way you can
implement your specialized type of list once, rather than repeat the
logic for all the different types of Ecore lists...
>
> Then JET templates should declared the new version of the EObjectList
> or EContainmentList when having a EReference to Many ( or a
> multi-valued feature List, is this the right thing to call it?)
> Again, thanks for your comments and looking forward the next ones :)
> Javi
>
Re: Copy On Write EList [message #1071980 is a reply to message #705220] Sun, 21 July 2013 15:54 Go to previous messageGo to next message
Eclipse UserFriend
Hi! I know it has been a long time, but i would like to leave here my expirience on creating a threadSafe Model.

First, no proxy are use in this model. So there may be other side effects for that.

The first for the Elist was to modifiy the templates to create wrappers for the Elist and return the wrappers instead of the Elist. This solution was suggested here, and it works, with the side effect of deadlocks (but when dealing with multithreading is already a posibility). Unfortunatelly i did not have the time and i did not have to time to improve this solution by looking into DelegatingEcoreEList (project time constrains as usual) and i would not know were to start ( Some remarks would be aprreceated in case i have the time to revisit this).

The template generation also add thread safe by either using locks or Actors. This code and the EList wrappers are activated by an annotation in the model.

The next issue was the Adapters List in the EObject, for this I replace the implementation of the EAdapterList with my implementation (included at the end of the post, no guaratees but it is what we used with EMF 2.7.0).

Then some problems appear in the Edit Framework with the notifiers, so the solution was to replace the ChangeNotifier Implementation by a ConcurrentChangeNotifier and a ConcurrentComposedAdapterFactory with use a CopyOnWriteArraList to store the listeners.


Cheers,


public class SynchronizedCopyOnWriteAdapterEList<E extends Object & Adapter> extends SynchronizedCopyOnWriteEList<E> implements EObservableAdapterList{

	private static final long serialVersionUID = -3748233483677878593L;

	protected Notifier notifier;

	protected CopyOnWriteArrayList<Listener> listeners;

	public SynchronizedCopyOnWriteAdapterEList(Notifier notifier)
	{
		this.notifier = notifier;
	}

	@Override
	protected boolean canContainNull()
	{
		return false;
	}

	@Override
	protected boolean useEquals()
	{
		return false;
	}

	@Override
	protected Object [] newData(int capacity)
	{
		return new Adapter [capacity];
	}

	@Override
	protected void didAdd(int index, E newObject)
	{
		if (listeners != null)
		{
			for (Listener listener : listeners)
			{
				listener.added(notifier, newObject);
			}
		}
		newObject.setTarget(notifier);
	}

	@Override
	protected void didRemove(int index, E oldObject)
	{
		if (listeners != null)
		{
			for (Listener listener : listeners)
			{
				listener.removed(notifier, oldObject);
			}
		}
		E adapter = oldObject;
		if (notifier.eDeliver())
		{
			Notification notification = 
					new NotificationImpl(Notification.REMOVING_ADAPTER, oldObject, null, index)
			{
				@Override
				public Object getNotifier()
				{
					return notifier;
				}
			};
			adapter.notifyChanged(notification);
		}
		if (adapter instanceof Adapter.Internal)
		{
			((Adapter.Internal)adapter).unsetTarget(notifier);
		}
		else if (adapter.getTarget() == notifier) 
		{
			adapter.setTarget(null);
		}
	}

	public void addListener(Listener listener)
	{
		listeners.add(listener);
	}

	public void removeListener(Listener listener)
	{
		listeners.remove(listener);
	}
	    
}

ublic class SynchronizedCopyOnWriteEList<E> extends BasicEList<E>{

	/**
	 * 
	 */
	private static final long serialVersionUID = 5770072918610284023L;
	final protected ReentrantLock notificationLock = new ReentrantLock(); 
	volatile protected CopyOnWriteArrayList<Object> data = new CopyOnWriteArrayList<Object>(); 

	/**
	 * Creates an empty instance with no initial capacity.
	 * The data storage will be null.
	 */
	public SynchronizedCopyOnWriteEList() 
	{
		super();
	}

	/**
	 * Creates an empty instance with the given capacity.
	 * @param initialCapacity the initial capacity of the list before it must grow.
	 * @exception IllegalArgumentException if the <code>initialCapacity</code> is negative.
	 */
	public SynchronizedCopyOnWriteEList(int initialCapacity) 
	{
		if (initialCapacity < 0)
		{
			throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);  
		}
		//	    data = newData(initialCapacity); 
	}

	/**
	 * Creates an instance that is a copy of the collection.
	 * @param collection the initial contents of the list.
	 */
	public SynchronizedCopyOnWriteEList(Collection<? extends E> collection) 
	{
		data.addAll(collection);  
	}

	/**
	 * Creates an initialized instance that directly uses the given arguments.
	 * @param size the size of the list.
	 * @param data the underlying storage of the list.
	 */
	protected SynchronizedCopyOnWriteEList(int size, Object [] data) 
	{
		this.data.addAll(Arrays.asList(data)); 
	}

	/**
	 * Returns new allocated data storage.
	 * Clients may override this to create typed storage.
	 * The cost of type checking via a typed array is negligible.
	 * @return new data storage.
	 */
	protected Object [] newData(int capacity)
	{
		return new Object [capacity];
	}

	/**
	 * Assigns the object into the data storage at the given index and returns the object that's been stored.
	 * Clients can monitor access to the storage via this method.
	 * @param index the position of the new content.
	 * @param object the new content.
	 * @return the object that's been stored.
	 * 
	 */
	protected E assign(int index, E object)
	{
		data.set(index, object);
		return object;
	}

	/**
	 * Returns the number of objects in the list.
	 * @return the number of objects in the list.
	 */
	@Override
	public int size() 
	{
		return data.size();
	}

	/**
	 * Returns whether the list has zero size.
	 * @return whether the list has zero size.
	 */
	@Override
	public boolean isEmpty() 
	{
		return data.isEmpty();
	}

	/**
	 * Returns whether the list contains the object.
	 * This implementation uses either <code>equals</code> or <code>"=="</code> depending on {@link #useEquals useEquals}.
	 * @param object the object in question.
	 * @return whether the list contains the object.
	 * @see #useEquals
	 */
	@Override
	public boolean contains(Object object) 
	{

		if (useEquals() && object != null)
		{
			return data.contains(object);
		}
		else
		{
			for (Object element : data) if (element == object) return true;
		}

		return false;
	}

	/**
	 * Returns the position of the first occurrence of the object in the list.
	 * This implementation uses either <code>equals</code> or <code>"=="</code> depending on {@link #useEquals useEquals}.
	 * @param object the object in question.
	 * @return the position of the first occurrence of the object in the list.
	 */
	@Override
	public int indexOf(Object object) 
	{

		if (useEquals() && object != null)
		{
			return data.indexOf(object);
		}
		else
		{
			int index = 0;
			for (Object element : data){
				if (element == object) return index;
				index++;
			}
		}

		return -1;
	}

	/**
	 * Returns the position of the last occurrence of the object in the list.
	 * This implementation uses either <code>equals</code> or <code>"=="</code> depending on {@link #useEquals useEquals}.
	 * @param object the object in question.
	 * @return the position of the last occurrence of the object in the list.
	 */
	@Override
	public int lastIndexOf(Object object) 
	{
		return data.lastIndexOf(object);
	}

	/**
	 * Returns an array containing all the objects in sequence.
	 * Clients may override {@link #newData newData} to create typed storage in this case.
	 * @return an array containing all the objects in sequence.
	 * @see #newData
	 */
	@Override
	public Object[] toArray() 
	{
		return data.toArray();
	}

	/**
	 * Returns an array containing all the objects in sequence.
	 * @param array the array that will be filled and returned, if it's big enough;
	 * otherwise, a suitably large array of the same type will be allocated and used instead.
	 * @return an array containing all the objects in sequence.
	 * @see #newData
	 */
	@Override
	public <T> T[] toArray(T[] array) 
	{
		return data.toArray(array);
	}

	/**
	 * Returns direct <b>unsafe</b> access to the underlying data storage.
	 * Clients may <b>not</b> modify this 
	 * and may <b>not</b> assume that the array remains valid as the list is modified.
	 * @return direct <b>unsafe</b> access to the underlying data storage.
	 */
	public Object [] data()
	{
		return data.toArray();
	}

	/**
	 * Updates directly and <b>unsafely</b> the underlying data storage.
	 * Clients <b>must</b> be aware that this subverts all callbacks 
	 * and hence possibly the integrity of the list. 
	 */
	public void setData(int size, Object [] data)
	{
		CopyOnWriteArrayList<Object> newList = new CopyOnWriteArrayList<Object>();
		newList.addAll(Arrays.asList(data));

		CopyOnWriteArrayList<Object> oldList = this.data;
		this.data = newList;
		oldList.clear();
		oldList = null;
	}

	/**
	 * An IndexOutOfBoundsException that constructs a message from the argument data.
	 * Having this avoids having the byte code that computes the message repeated/inlined at the creation site.
	 */
	protected static class BasicIndexOutOfBoundsException extends AbstractEList.BasicIndexOutOfBoundsException
	{
		private static final long serialVersionUID = 1L;

		/**
		 * Constructs an instance with a message based on the arguments.
		 */
		public BasicIndexOutOfBoundsException(int index, int size)
		{
			super(index, size);
		}
	}

	/**
	 * Returns the object at the index.
	 * This implementation delegates to {@link #resolve resolve} 
	 * so that clients may transform the fetched object.
	 * @param index the position in question.
	 * @return the object at the index.
	 * @exception IndexOutOfBoundsException if the index isn't within the size range.
	 * @see #resolve
	 * @see #basicGet
	 */
	@SuppressWarnings("unchecked")
	@Override
	public E get(int index) 
	{
		if (index >= size)
			throw new BasicIndexOutOfBoundsException(index, size);

		return resolve(index, (E)data.get(index));
	}

	/**
	 * Returns the object at the index without {@link #resolve resolving} it.
	 * @param index the position in question.
	 * @return the object at the index.
	 * @exception IndexOutOfBoundsException if the index isn't within the size range.
	 * @see #resolve
	 * @see #get
	 */
	@Override
	public E basicGet(int index)
	{
		if (index >= size)
			throw new BasicIndexOutOfBoundsException(index, size);

		return primitiveGet(index);
	}

	/**
	 * Returns the object at the index without {@link #resolve resolving} it and without range checking the index.
	 * @param index the position in question.
	 * @return the object at the index.
	 * @see #resolve
	 * @see #get
	 * @see #basicGet(int)
	 */
	@SuppressWarnings("unchecked")
	@Override
	protected E primitiveGet(int index)
	{
		return (E)data.get(index);
	}

	/**
	 * Sets the object at the index 
	 * and returns the old object at the index;
	 * it does no ranging checking or uniqueness checking.
	 * This implementation delegates to {@link #assign assign}, {@link #didSet didSet}, and {@link #didChange didChange}.
	 * @param index the position in question.
	 * @param object the object to set.
	 * @return the old object at the index.
	 * @see #set
	 */
	@Override
	public E setUnique(int index, E object)
	{
		notificationLock.lock();
		try{
			@SuppressWarnings("unchecked") E oldObject = (E)data.get(index);
			assign(index, validate(index, object));
			didSet(index, object, oldObject);
			didChange();
			return oldObject;
		}finally{
			notificationLock.unlock();
		}
	}

	/**
	 * Adds the object at the end of the list;
	 * it does no uniqueness checking.
	 * This implementation delegates to {@link #assign assign}, {@link #didAdd didAdd}, and {@link #didChange didChange}.
	 * after uniqueness checking.
	 * @param object the object to be added.
	 * @see #add(Object)
	 */
	@Override
	public void addUnique(E object) 
	{
		notificationLock.lock();
		try{
			int newIndex = data.size();
			data.add(validate(newIndex, object));
			didAdd(newIndex, object);
			didChange();
		}finally{
			notificationLock.unlock();
		}
	}

	/**
	 * Adds the object at the given index in the list;
	 * it does no ranging checking or uniqueness checking.
	 * This implementation delegates to {@link #assign assign}, {@link #didAdd didAdd}, and {@link #didChange didChange}.
	 * @param object the object to be added.
	 * @see #add(int, Object)
	 */
	@Override
	public void addUnique(int index, E object) 
	{
		notificationLock.lock();
		try{
			data.add(index, validate(data.size(), object));
			didAdd(index, object);
			didChange();
		}finally{
			notificationLock.unlock();
		}
	}

	/**
	 * Adds each object of the collection to the end of the list;
	 * it does no uniqueness checking.
	 * This implementation delegates to {@link #assign assign}, {@link #didAdd didAdd}, and {@link #didChange didChange}.
	 * @param collection the collection of objects to be added.
	 * @see #addAll(Collection)
	 */
	@Override
	public boolean addAllUnique(Collection<? extends E> collection) 
	{
		
		for ( E object: collection)
		{
			addUnique(object);
		}
		
		return collection.size() != 0;
	}

	/**
	 * Adds each object of the collection at each successive index in the list 
	 * and returns whether any objects were added;
	 * it does no ranging checking or uniqueness checking.
	 * This implementation delegates to {@link #assign assign}, {@link #didAdd didAdd}, and {@link #didChange didChange}.
	 * @param index the index at which to add.
	 * @param collection the collection of objects to be added.
	 * @return whether any objects were added.
	 * @see #addAll(int, Collection)
	 */
	@Override
	public boolean addAllUnique(int index, Collection<? extends E> collection) 
	{
		int insertIndex = index;
		for ( E object: collection)
		{
			addUnique(insertIndex, object);
			insertIndex++;
		}
		
		return collection.size() != 0;
	}

	/**
	 * Adds each object from start to end of the array at the index of list 
	 * and returns whether any objects were added;
	 * it does no ranging checking or uniqueness checking.
	 * This implementation delegates to {@link #assign assign}, {@link #didAdd didAdd}, and {@link #didChange didChange}.
	 * @param objects the objects to be added.
	 * @param start the index of first object to be added.
	 * @param end the index past the last object to be added.
	 * @return whether any objects were added.
	 * @see #addAllUnique(Object[], int, int)
	 */
	@Override
	public boolean addAllUnique(Object [] objects, int start, int end) 
	{
		for (int i = start; i < end; ++i)
		{
			@SuppressWarnings("unchecked") E object = (E)objects[i];
			addUnique(object);
		}

		return (end - start) != 0;
	}

	/**
	 * Adds each object from start to end of the array at each successive index in the list 
	 * and returns whether any objects were added;
	 * it does no ranging checking or uniqueness checking.
	 * This implementation delegates to {@link #assign assign}, {@link #didAdd didAdd}, and {@link #didChange didChange}.
	 * @param index the index at which to add.
	 * @param objects the objects to be added.
	 * @param start the index of first object to be added.
	 * @param end the index past the last object to be added.
	 * @return whether any objects were added.
	 * @see #addAllUnique(Object[], int, int)
	 */
	@Override
	public boolean addAllUnique(int index, Object [] objects, int start, int end) 
	{
		int insertIndex = index;
		for (int i = start; i < end; ++i)
		{
			@SuppressWarnings("unchecked") E object = (E)objects[i];
			addUnique(insertIndex, object);
			insertIndex++;
		}

		return (end - start) != 0;
	}

	/**
	 * Removes the object at the index from the list and returns it.
	 * This implementation delegates to {@link #didRemove didRemove} and {@link #didChange didChange}.
	 * @param index the position of the object to remove.
	 * @return the removed object.
	 * @exception IndexOutOfBoundsException if the index isn't within the size range.
	 */
	@Override
	public E remove(int index) 
	{
		notificationLock.lock();
		try{
			@SuppressWarnings("unchecked") E oldObject = (E) data.remove(index);
			didRemove(index, oldObject);
			didChange();
			return oldObject;
		}finally{
			notificationLock.unlock();
		}
	}

	/**
	 * Clears the list of all objects.
	 * This implementation discards the data storage without modifying it
	 * and delegates to {@link #didClear didClear} and {@link #didChange didChange}.
	 */
	@Override
	public void clear() 
	{
		notificationLock.lock();
		try{
			Object [] oldData = data.toArray();
			int oldSize = oldData.length;
			data.clear();
			didClear(oldSize, oldData);
			didChange();
		}finally{
			notificationLock.unlock();
		}
	}

	/**
	 * Moves the object at the source index of the list to the target index of the list
	 * and returns the moved object.
	 * This implementation delegates to {@link #assign assign}, {@link #didMove didMove}, and {@link #didChange didChange}.
	 * @param targetIndex the new position for the object in the list.
	 * @param sourceIndex the old position of the object in the list.
	 * @return the moved object.
	 * @exception IndexOutOfBoundsException if either index isn't within the size range.
	 */
	@Override
	public E move(int targetIndex, int sourceIndex)
	{
		notificationLock.lock();
		try{
			@SuppressWarnings("unchecked") E object = (E)data.remove(sourceIndex);
			data.add(targetIndex, object);
			didMove(targetIndex, object, sourceIndex);
			didChange();
			return object;
		}finally{
			notificationLock.unlock();
		}
	}

	/**
	 * Shrinks the capacity of the list to the minimal requirements.
	 * @see #grow
	 */
	public void shrink() 
	{
		// this is done by the arrayList.
	}

	/**
	 * Grows the capacity of the list 
	 * to ensure that no additional growth is needed until the size exceeds the specified minimum capacity.
	 * @see #shrink
	 */
	public void grow(int minimumCapacity) 
	{
		// this is done by the arrayList.
	}

	private synchronized void writeObject(ObjectOutputStream objectOutputStream) throws IOException
	{
		objectOutputStream.defaultWriteObject();
		if (data.isEmpty())
		{
			objectOutputStream.writeInt(0);
		}
		else
		{
			Object [] snapshotData = data.toArray();
			objectOutputStream.writeInt(snapshotData.length);
			for (int i = 0; i < snapshotData.length; ++i)
			{
				objectOutputStream.writeObject(snapshotData[i]);
			}
		}
	}

	private synchronized void readObject(ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException
	{
		data.clear();
		objectInputStream.defaultReadObject();
		int arrayLength = objectInputStream.readInt();
		if (arrayLength > 0)
		{
			for (int i = 0; i < arrayLength; ++i)
			{
				notificationLock.lock();
				try{
					@SuppressWarnings("unchecked") E object = (E)objectInputStream.readObject();
					data.add(validate(i, object));
					didAdd(i, object);
				}finally{
					notificationLock.unlock();
				}
			}
		}
	}

	/**
	 * Returns a shallow copy of this list.
	 * @return a shallow copy of this list.
	 */
	@Override
	public Object clone() 
	{
		SynchronizedCopyOnWriteEList<E> clone = new SynchronizedCopyOnWriteEList<E>();
		clone.data.addAll(this.data);
		return clone;
	}

	/**
	 * An extensible iterator implementation.
	 * @deprecated 
	 * @see AbstractEList.EIterator
	 */
	@Deprecated
	protected class EIterator<E1> extends AbstractEList<E>.EIterator<E1>
	{
		// Pointless extension
	}

	/**
	 * An extended read-only iterator that does not {@link BasicEList#resolve resolve} objects.
	 * @deprecated
	 * @see AbstractEList.NonResolvingEIterator
	 */
	@Deprecated
	protected class NonResolvingEIterator<E1> extends AbstractEList<E>.NonResolvingEIterator<E1>
	{
		// Pointless extension
	}

	/**
	 * An extensible list iterator implementation.
	 * @deprecated
	 * @see AbstractEList.EListIterator
	 */
	@Deprecated
	protected class EListIterator<E1> extends AbstractEList<E>.EListIterator<E1>
	{
		/**
		 * Creates an instance.
		 */
		public EListIterator() 
		{
			super();
		}

		/**
		 * Creates an instance advanced to the index.
		 * @param index the starting index.
		 */
		public EListIterator(int index) 
		{
			super(index);
			cursor = index;
		}
	}

	/**
	 * An extended read-only list iterator that does not {@link BasicEList#resolve resolve} objects.
	 * @deprecated
	 * @see AbstractEList.NonResolvingEListIterator
	 */
	@Deprecated
	protected class NonResolvingEListIterator<E1> extends AbstractEList<E>.NonResolvingEListIterator<E1>
	{
		/**
		 * Creates an instance.
		 */
		public NonResolvingEListIterator()
		{
			super();
		}

		/**
		 * Creates an instance advanced to the index.
		 * @param index the starting index.
		 */
		public NonResolvingEListIterator(int index) 
		{
			super(index);
		}
	}

	/**
	 * An unmodifiable version of {@link BasicEList}.
	 */
	public static class UnmodifiableEList<E> extends SynchronizedCopyOnWriteEList<E>
	{
		private static final long serialVersionUID = 1L;

		/**
		 * Creates an initialized instance.
		 * @param size the size of the list.
		 * @param data the underlying storage of the list.
		 */
		public UnmodifiableEList(Object [] data) 
		{
			this.data.addAll(Arrays.asList(data));
		}

		/**
		 * Throws an exception.
		 * @exception UnsupportedOperationException always because it's not supported.
		 */
		@Override
		public E set(int index, E object) 
		{
			throw new UnsupportedOperationException();
		}

		/**
		 * Throws an exception.
		 * @exception UnsupportedOperationException always because it's not supported.
		 */
		@Override
		public boolean add(E object) 
		{
			throw new UnsupportedOperationException();
		}

		/**
		 * Throws an exception.
		 * @exception UnsupportedOperationException always because it's not supported.
		 */
		@Override
		public void add(int index, E object) 
		{
			throw new UnsupportedOperationException();
		}

		/**
		 * Throws an exception.
		 * @exception UnsupportedOperationException always because it's not supported.
		 */
		@Override
		public boolean addAll(Collection<? extends E> collection) 
		{
			throw new UnsupportedOperationException();
		}

		/**
		 * Throws an exception.
		 * @exception UnsupportedOperationException always because it's not supported.
		 */
		@Override
		public boolean addAll(int index, Collection<? extends E> collection) 
		{
			throw new UnsupportedOperationException();
		}

		/**
		 * Throws an exception.
		 * @exception UnsupportedOperationException always because it's not supported.
		 */
		@Override
		public boolean remove(Object object) 
		{
			throw new UnsupportedOperationException();
		}

		/**
		 * Throws an exception.
		 * @exception UnsupportedOperationException always because it's not supported.
		 */
		@Override
		public E remove(int index) 
		{
			throw new UnsupportedOperationException();
		}

		/**
		 * Throws an exception.
		 * @exception UnsupportedOperationException always because it's not supported.
		 */
		@Override
		public boolean removeAll(Collection<?> collection) 
		{
			throw new UnsupportedOperationException();
		}

		/**
		 * Throws an exception.
		 * @exception UnsupportedOperationException always because it's not supported.
		 */
		@Override
		public boolean retainAll(Collection<?> collection) 
		{
			throw new UnsupportedOperationException();
		}

		/**
		 * Throws an exception.
		 * @exception UnsupportedOperationException always because it's not supported.
		 */
		@Override
		public void clear() 
		{
			throw new UnsupportedOperationException();
		}

		/**
		 * Throws an exception.
		 * @exception UnsupportedOperationException always because it's not supported.
		 */
		@Override
		public void move(int index, E object) 
		{
			throw new UnsupportedOperationException();
		}

		/**
		 * Throws an exception.
		 * @exception UnsupportedOperationException always because it's not supported.
		 */
		@Override
		public E move(int targetIndex, int sourceIndex)
		{
			throw new UnsupportedOperationException();
		}

		/**
		 * Throws an exception.
		 * @exception UnsupportedOperationException always because it's not supported.
		 */
		@Override
		public void shrink() 
		{
			throw new UnsupportedOperationException();
		}

		/**
		 * Throws an exception.
		 * @exception UnsupportedOperationException always because it's not supported.
		 */
		@Override
		public void grow(int minimumCapacity) 
		{
			throw new UnsupportedOperationException();
		}

		/**
		 * Returns the {@link BasicEList#basicIterator basic iterator}.
		 * @return the basic iterator.
		 */
		@Override
		public Iterator<E> iterator() 
		{
			return basicIterator();
		}

		/**
		 * Returns the {@link #basicListIterator() basic list iterator}.
		 * @return the basic list iterator.
		 */
		@Override
		public ListIterator<E> listIterator() 
		{
			return basicListIterator();
		}

		/**
		 * Returns the {@link #basicListIterator(int) basic list iterator} advanced to the index.
		 * @param index the starting index.
		 * @return the basic list iterator.
		 */
		@Override
		public ListIterator<E> listIterator(int index) 
		{
			return basicListIterator(index);
		}
	}

	/**
	 * Returns an <b>unsafe</b> list that provides a {@link #resolve non-resolving} view of the underlying data storage.
	 * @return an <b>unsafe</b> list that provides a non-resolving view of the underlying data storage.
	 */
	@Override
	protected List<E> basicList()
	{
		if (size == 0)
		{
			return ECollections.emptyEList();
		}
		else
		{
			return new SynchronizedCopyOnWriteEList.UnmodifiableEList<E>(data.toArray());
		}
	}

	/**
	 * A <code>BasicEList</code> that {@link #useEquals uses} <code>==</code> instead of <code>equals</code> to compare members.
	 */
	public static class FastCompare<E> extends BasicEList<E>
	{
		private static final long serialVersionUID = 1L;

		/**
		 * Creates an empty instance with no initial capacity.
		 */
		public FastCompare()
		{
			super();
		}

		/**
		 * Creates an empty instance with the given capacity.
		 * @param initialCapacity the initial capacity of the list before it must grow.
		 * @exception IllegalArgumentException if the <code>initialCapacity</code> is negative.
		 */
		public FastCompare(int initialCapacity)
		{
			super(initialCapacity);
		}

		/**
		 * Creates an instance that is a copy of the collection.
		 * @param collection the initial contents of the list.
		 */
		public FastCompare(Collection<? extends E> collection)
		{
			super(collection.size());
			addAll(collection);
		}

		/**
		 * Returns <code>false</code> because this list uses <code>==</code>.
		 * @return <code>false</code>.
		 */
		@Override
		protected boolean useEquals()
		{
			return false;
		}
	}
	
	  /**
	   * Returns an iterator.
	   * This implementation allocates a {@link AbstractEList.EIterator}.
	   * @return an iterator.
	   * @see AbstractEList.EIterator
	   */
	  @Override
	  public Iterator<E> iterator()
	  {
	    return new SynchronizedCopyOnWriteEIterator();
	  }

	  /**
	   * An extensible iterator implementation.
	   */
	  protected class SynchronizedCopyOnWriteEIterator implements Iterator<E>
	  {
		  int index = -1;
		 Iterator<Object> internalIterator = data.iterator();
		  
		@Override
		public boolean hasNext() {
			return internalIterator.hasNext();
		}

		@SuppressWarnings("unchecked")
		@Override
		public E next() {
			index++;
			E object = (E)internalIterator.next();
			return resolve(index, object);
		}

		@Override
		public void remove() {
			internalIterator.remove();
		}
	  }

	  /**
	   * Returns a read-only iterator that does not {@link #resolve resolve} objects.
	   * This implementation allocates a {@link NonResolvingEIterator}.
	   * @return a read-only iterator that does not resolve objects.
	   */
	  protected Iterator<E> basicIterator()
	  {
	    return new NonResolvingSynchronizedCopyOnWriteEIterator();
	  }

	  /**
	   * An extended read-only iterator that does not {@link AbstractEList#resolve resolve} objects.
	   */
	  protected class NonResolvingSynchronizedCopyOnWriteEIterator extends SynchronizedCopyOnWriteEIterator
	  {
		  @SuppressWarnings("unchecked")
		  @Override
		  public E next() {
			  index++;
			  return (E)internalIterator.next();
		  }

		  /**
		   * Throws and exception.
		   * @exception UnsupportedOperationException always because it's not supported.
		   */
		  @Override
		  public void remove()
		  {
			  throw new UnsupportedOperationException();
		  }
	  }
}


Re: Copy On Write EList [message #1072162 is a reply to message #1071980] Mon, 22 July 2013 03:56 Go to previous messageGo to next message
Eclipse UserFriend
Xavipen,

Comments below.

On 21/07/2013 9:54 PM, Xavipen Mising name wrote:
> Hi! I know it has been a long time, but i would like to leave here my
> expirience on creating a threadSafe Model.
>
> First, no proxy are use in this model. So there may be other side
> effects for that.
>
> The first for the Elist was to modifiy the templates to create
> wrappers for the Elist and return the wrappers instead of the Elist.
> This solution was suggested here, and it works, with the side effect
> of deadlocks (but when dealing with multithreading is already a
> posibility). Unfortunatelly i did not have the time and i did not have
> to time to improve this solution by looking into DelegatingEcoreEList
> (project time constrains as usual) and i would not know were to start
> ( Some remarks would be aprreceated in case i have the time to revisit
> this).
In general, it seems to me not possible to make a multi-threaded
application well behaved simply by making primitives thread safe. E.g.,
two threads operating on the opposite ends of a bidirectional reference
will tend to deadlock. Do the wrappers implement InternalEList and
EStructuralFeature.Setting?

Two threads both doing an addAll at some index will not generally
produce atomic results because none of the addAll methods lock over the
whole operation.
>
> The template generation also add thread safe by either using locks or
> Actors. This code and the EList wrappers are activated by an
> annotation in the model.
>
> The next issue was the Adapters List in the EObject, for this I
> replace the implementation of the EAdapterList with my implementation
> (included at the end of the post, no guaratees but it is what we used
> with EMF 2.7.0).
This is an example of what I mean. Often an adapter factory will check
if an adapter is already present and then add it if not. Even if the
list is thread safe, the adapter can end up being added twice...
>
> Then some problems appear in the Edit Framework with the notifiers, so
> the solution was to replace the ChangeNotifier Implementation by a
> ConcurrentChangeNotifier and a ConcurrentComposedAdapterFactory with
> use a CopyOnWriteArraList to store the listeners.
>
>
> Cheers,
>
>
>
> public class SynchronizedCopyOnWriteAdapterEList<E extends Object &
> Adapter> extends SynchronizedCopyOnWriteEList<E> implements
> EObservableAdapterList{
>
> private static final long serialVersionUID = -3748233483677878593L;
>
> protected Notifier notifier;
>
> protected CopyOnWriteArrayList<Listener> listeners;
>
> public SynchronizedCopyOnWriteAdapterEList(Notifier notifier)
> {
> this.notifier = notifier;
> }
>
> @Override
> protected boolean canContainNull()
> {
> return false;
> }
>
> @Override
> protected boolean useEquals()
> {
> return false;
> }
>
> @Override
> protected Object [] newData(int capacity)
> {
> return new Adapter [capacity];
> }
>
> @Override
> protected void didAdd(int index, E newObject)
> {
> if (listeners != null)
> {
> for (Listener listener : listeners)
> {
> listener.added(notifier, newObject);
> }
> }
> newObject.setTarget(notifier);
> }
>
> @Override
> protected void didRemove(int index, E oldObject)
> {
> if (listeners != null)
> {
> for (Listener listener : listeners)
> {
> listener.removed(notifier, oldObject);
> }
> }
> E adapter = oldObject;
> if (notifier.eDeliver())
> {
> Notification notification = new
> NotificationImpl(Notification.REMOVING_ADAPTER, oldObject, null, index)
> {
> @Override
> public Object getNotifier()
> {
> return notifier;
> }
> };
> adapter.notifyChanged(notification);
> }
> if (adapter instanceof Adapter.Internal)
> {
> ((Adapter.Internal)adapter).unsetTarget(notifier);
> }
> else if (adapter.getTarget() == notifier) {
> adapter.setTarget(null);
> }
> }
>
> public void addListener(Listener listener)
> {
> listeners.add(listener);
> }
>
> public void removeListener(Listener listener)
> {
> listeners.remove(listener);
> }
> }
>
> ublic class SynchronizedCopyOnWriteEList<E> extends BasicEList<E>{
>
> /**
> * */
> private static final long serialVersionUID = 5770072918610284023L;
> final protected ReentrantLock notificationLock = new
> ReentrantLock(); volatile protected CopyOnWriteArrayList<Object>
> data = new CopyOnWriteArrayList<Object>();
> /**
> * Creates an empty instance with no initial capacity.
> * The data storage will be null.
> */
> public SynchronizedCopyOnWriteEList() {
> super();
> }
>
> /**
> * Creates an empty instance with the given capacity.
> * @param initialCapacity the initial capacity of the list before
> it must grow.
> * @exception IllegalArgumentException if the
> <code>initialCapacity</code> is negative.
> */
> public SynchronizedCopyOnWriteEList(int initialCapacity) {
> if (initialCapacity < 0)
> {
> throw new IllegalArgumentException("Illegal Capacity: " +
> initialCapacity); }
> // data = newData(initialCapacity); }
>
> /**
> * Creates an instance that is a copy of the collection.
> * @param collection the initial contents of the list.
> */
> public SynchronizedCopyOnWriteEList(Collection<? extends E>
> collection) {
> data.addAll(collection); }
>
> /**
> * Creates an initialized instance that directly uses the given
> arguments.
> * @param size the size of the list.
> * @param data the underlying storage of the list.
> */
> protected SynchronizedCopyOnWriteEList(int size, Object [] data)
> {
> this.data.addAll(Arrays.asList(data)); }
>
> /**
> * Returns new allocated data storage.
> * Clients may override this to create typed storage.
> * The cost of type checking via a typed array is negligible.
> * @return new data storage.
> */
> protected Object [] newData(int capacity)
> {
> return new Object [capacity];
> }
>
> /**
> * Assigns the object into the data storage at the given index and
> returns the object that's been stored.
> * Clients can monitor access to the storage via this method.
> * @param index the position of the new content.
> * @param object the new content.
> * @return the object that's been stored.
> * */
> protected E assign(int index, E object)
> {
> data.set(index, object);
> return object;
> }
>
> /**
> * Returns the number of objects in the list.
> * @return the number of objects in the list.
> */
> @Override
> public int size() {
> return data.size();
> }
>
> /**
> * Returns whether the list has zero size.
> * @return whether the list has zero size.
> */
> @Override
> public boolean isEmpty() {
> return data.isEmpty();
> }
>
> /**
> * Returns whether the list contains the object.
> * This implementation uses either <code>equals</code> or
> <code>"=="</code> depending on {@link #useEquals useEquals}.
> * @param object the object in question.
> * @return whether the list contains the object.
> * @see #useEquals
> */
> @Override
> public boolean contains(Object object) {
>
> if (useEquals() && object != null)
> {
> return data.contains(object);
> }
> else
> {
> for (Object element : data) if (element == object) return
> true;
> }
>
> return false;
> }
>
> /**
> * Returns the position of the first occurrence of the object in
> the list.
> * This implementation uses either <code>equals</code> or
> <code>"=="</code> depending on {@link #useEquals useEquals}.
> * @param object the object in question.
> * @return the position of the first occurrence of the object in
> the list.
> */
> @Override
> public int indexOf(Object object) {
>
> if (useEquals() && object != null)
> {
> return data.indexOf(object);
> }
> else
> {
> int index = 0;
> for (Object element : data){
> if (element == object) return index;
> index++;
> }
> }
>
> return -1;
> }
>
> /**
> * Returns the position of the last occurrence of the object in
> the list.
> * This implementation uses either <code>equals</code> or
> <code>"=="</code> depending on {@link #useEquals useEquals}.
> * @param object the object in question.
> * @return the position of the last occurrence of the object in
> the list.
> */
> @Override
> public int lastIndexOf(Object object) {
> return data.lastIndexOf(object);
> }
>
> /**
> * Returns an array containing all the objects in sequence.
> * Clients may override {@link #newData newData} to create typed
> storage in this case.
> * @return an array containing all the objects in sequence.
> * @see #newData
> */
> @Override
> public Object[] toArray() {
> return data.toArray();
> }
>
> /**
> * Returns an array containing all the objects in sequence.
> * @param array the array that will be filled and returned, if
> it's big enough;
> * otherwise, a suitably large array of the same type will be
> allocated and used instead.
> * @return an array containing all the objects in sequence.
> * @see #newData
> */
> @Override
> public <T> T[] toArray(T[] array) {
> return data.toArray(array);
> }
>
> /**
> * Returns direct <b>unsafe</b> access to the underlying data
> storage.
> * Clients may <b>not</b> modify this * and may <b>not</b>
> assume that the array remains valid as the list is modified.
> * @return direct <b>unsafe</b> access to the underlying data
> storage.
> */
> public Object [] data()
> {
> return data.toArray();
> }
>
> /**
> * Updates directly and <b>unsafely</b> the underlying data storage.
> * Clients <b>must</b> be aware that this subverts all callbacks
> * and hence possibly the integrity of the list. */
> public void setData(int size, Object [] data)
> {
> CopyOnWriteArrayList<Object> newList = new
> CopyOnWriteArrayList<Object>();
> newList.addAll(Arrays.asList(data));
>
> CopyOnWriteArrayList<Object> oldList = this.data;
> this.data = newList;
> oldList.clear();
> oldList = null;
> }
>
> /**
> * An IndexOutOfBoundsException that constructs a message from the
> argument data.
> * Having this avoids having the byte code that computes the
> message repeated/inlined at the creation site.
> */
> protected static class BasicIndexOutOfBoundsException extends
> AbstractEList.BasicIndexOutOfBoundsException
> {
> private static final long serialVersionUID = 1L;
>
> /**
> * Constructs an instance with a message based on the arguments.
> */
> public BasicIndexOutOfBoundsException(int index, int size)
> {
> super(index, size);
> }
> }
>
> /**
> * Returns the object at the index.
> * This implementation delegates to {@link #resolve resolve}
> * so that clients may transform the fetched object.
> * @param index the position in question.
> * @return the object at the index.
> * @exception IndexOutOfBoundsException if the index isn't within
> the size range.
> * @see #resolve
> * @see #basicGet
> */
> @SuppressWarnings("unchecked")
> @Override
> public E get(int index) {
> if (index >= size)
> throw new BasicIndexOutOfBoundsException(index, size);
>
> return resolve(index, (E)data.get(index));
> }
>
> /**
> * Returns the object at the index without {@link #resolve
> resolving} it.
> * @param index the position in question.
> * @return the object at the index.
> * @exception IndexOutOfBoundsException if the index isn't within
> the size range.
> * @see #resolve
> * @see #get
> */
> @Override
> public E basicGet(int index)
> {
> if (index >= size)
> throw new BasicIndexOutOfBoundsException(index, size);
>
> return primitiveGet(index);
> }
>
> /**
> * Returns the object at the index without {@link #resolve
> resolving} it and without range checking the index.
> * @param index the position in question.
> * @return the object at the index.
> * @see #resolve
> * @see #get
> * @see #basicGet(int)
> */
> @SuppressWarnings("unchecked")
> @Override
> protected E primitiveGet(int index)
> {
> return (E)data.get(index);
> }
>
> /**
> * Sets the object at the index * and returns the old object
> at the index;
> * it does no ranging checking or uniqueness checking.
> * This implementation delegates to {@link #assign assign}, {@link
> #didSet didSet}, and {@link #didChange didChange}.
> * @param index the position in question.
> * @param object the object to set.
> * @return the old object at the index.
> * @see #set
> */
> @Override
> public E setUnique(int index, E object)
> {
> notificationLock.lock();
> try{
> @SuppressWarnings("unchecked") E oldObject =
> (E)data.get(index);
> assign(index, validate(index, object));
> didSet(index, object, oldObject);
> didChange();
> return oldObject;
> }finally{
> notificationLock.unlock();
> }
> }
>
> /**
> * Adds the object at the end of the list;
> * it does no uniqueness checking.
> * This implementation delegates to {@link #assign assign}, {@link
> #didAdd didAdd}, and {@link #didChange didChange}.
> * after uniqueness checking.
> * @param object the object to be added.
> * @see #add(Object)
> */
> @Override
> public void addUnique(E object) {
> notificationLock.lock();
> try{
> int newIndex = data.size();
> data.add(validate(newIndex, object));
> didAdd(newIndex, object);
> didChange();
> }finally{
> notificationLock.unlock();
> }
> }
>
> /**
> * Adds the object at the given index in the list;
> * it does no ranging checking or uniqueness checking.
> * This implementation delegates to {@link #assign assign}, {@link
> #didAdd didAdd}, and {@link #didChange didChange}.
> * @param object the object to be added.
> * @see #add(int, Object)
> */
> @Override
> public void addUnique(int index, E object) {
> notificationLock.lock();
> try{
> data.add(index, validate(data.size(), object));
> didAdd(index, object);
> didChange();
> }finally{
> notificationLock.unlock();
> }
> }
>
> /**
> * Adds each object of the collection to the end of the list;
> * it does no uniqueness checking.
> * This implementation delegates to {@link #assign assign}, {@link
> #didAdd didAdd}, and {@link #didChange didChange}.
> * @param collection the collection of objects to be added.
> * @see #addAll(Collection)
> */
> @Override
> public boolean addAllUnique(Collection<? extends E> collection) {
>
> for ( E object: collection)
> {
> addUnique(object);
> }
>
> return collection.size() != 0;
> }
>
> /**
> * Adds each object of the collection at each successive index in
> the list * and returns whether any objects were added;
> * it does no ranging checking or uniqueness checking.
> * This implementation delegates to {@link #assign assign}, {@link
> #didAdd didAdd}, and {@link #didChange didChange}.
> * @param index the index at which to add.
> * @param collection the collection of objects to be added.
> * @return whether any objects were added.
> * @see #addAll(int, Collection)
> */
> @Override
> public boolean addAllUnique(int index, Collection<? extends E>
> collection) {
> int insertIndex = index;
> for ( E object: collection)
> {
> addUnique(insertIndex, object);
> insertIndex++;
> }
>
> return collection.size() != 0;
> }
>
> /**
> * Adds each object from start to end of the array at the index of
> list * and returns whether any objects were added;
> * it does no ranging checking or uniqueness checking.
> * This implementation delegates to {@link #assign assign}, {@link
> #didAdd didAdd}, and {@link #didChange didChange}.
> * @param objects the objects to be added.
> * @param start the index of first object to be added.
> * @param end the index past the last object to be added.
> * @return whether any objects were added.
> * @see #addAllUnique(Object[], int, int)
> */
> @Override
> public boolean addAllUnique(Object [] objects, int start, int end)
> {
> for (int i = start; i < end; ++i)
> {
> @SuppressWarnings("unchecked") E object = (E)objects[i];
> addUnique(object);
> }
>
> return (end - start) != 0;
> }
>
> /**
> * Adds each object from start to end of the array at each
> successive index in the list * and returns whether any objects
> were added;
> * it does no ranging checking or uniqueness checking.
> * This implementation delegates to {@link #assign assign}, {@link
> #didAdd didAdd}, and {@link #didChange didChange}.
> * @param index the index at which to add.
> * @param objects the objects to be added.
> * @param start the index of first object to be added.
> * @param end the index past the last object to be added.
> * @return whether any objects were added.
> * @see #addAllUnique(Object[], int, int)
> */
> @Override
> public boolean addAllUnique(int index, Object [] objects, int
> start, int end) {
> int insertIndex = index;
> for (int i = start; i < end; ++i)
> {
> @SuppressWarnings("unchecked") E object = (E)objects[i];
> addUnique(insertIndex, object);
> insertIndex++;
> }
>
> return (end - start) != 0;
> }
>
> /**
> * Removes the object at the index from the list and returns it.
> * This implementation delegates to {@link #didRemove didRemove}
> and {@link #didChange didChange}.
> * @param index the position of the object to remove.
> * @return the removed object.
> * @exception IndexOutOfBoundsException if the index isn't within
> the size range.
> */
> @Override
> public E remove(int index) {
> notificationLock.lock();
> try{
> @SuppressWarnings("unchecked") E oldObject = (E)
> data.remove(index);
> didRemove(index, oldObject);
> didChange();
> return oldObject;
> }finally{
> notificationLock.unlock();
> }
> }
>
> /**
> * Clears the list of all objects.
> * This implementation discards the data storage without modifying it
> * and delegates to {@link #didClear didClear} and {@link
> #didChange didChange}.
> */
> @Override
> public void clear() {
> notificationLock.lock();
> try{
> Object [] oldData = data.toArray();
> int oldSize = oldData.length;
> data.clear();
> didClear(oldSize, oldData);
> didChange();
> }finally{
> notificationLock.unlock();
> }
> }
>
> /**
> * Moves the object at the source index of the list to the target
> index of the list
> * and returns the moved object.
> * This implementation delegates to {@link #assign assign}, {@link
> #didMove didMove}, and {@link #didChange didChange}.
> * @param targetIndex the new position for the object in the list.
> * @param sourceIndex the old position of the object in the list.
> * @return the moved object.
> * @exception IndexOutOfBoundsException if either index isn't
> within the size range.
> */
> @Override
> public E move(int targetIndex, int sourceIndex)
> {
> notificationLock.lock();
> try{
> @SuppressWarnings("unchecked") E object =
> (E)data.remove(sourceIndex);
> data.add(targetIndex, object);
> didMove(targetIndex, object, sourceIndex);
> didChange();
> return object;
> }finally{
> notificationLock.unlock();
> }
> }
>
> /**
> * Shrinks the capacity of the list to the minimal requirements.
> * @see #grow
> */
> public void shrink() {
> // this is done by the arrayList.
> }
>
> /**
> * Grows the capacity of the list * to ensure that no
> additional growth is needed until the size exceeds the specified
> minimum capacity.
> * @see #shrink
> */
> public void grow(int minimumCapacity) {
> // this is done by the arrayList.
> }
>
> private synchronized void writeObject(ObjectOutputStream
> objectOutputStream) throws IOException
> {
> objectOutputStream.defaultWriteObject();
> if (data.isEmpty())
> {
> objectOutputStream.writeInt(0);
> }
> else
> {
> Object [] snapshotData = data.toArray();
> objectOutputStream.writeInt(snapshotData.length);
> for (int i = 0; i < snapshotData.length; ++i)
> {
> objectOutputStream.writeObject(snapshotData[i]);
> }
> }
> }
>
> private synchronized void readObject(ObjectInputStream
> objectInputStream) throws IOException, ClassNotFoundException
> {
> data.clear();
> objectInputStream.defaultReadObject();
> int arrayLength = objectInputStream.readInt();
> if (arrayLength > 0)
> {
> for (int i = 0; i < arrayLength; ++i)
> {
> notificationLock.lock();
> try{
> @SuppressWarnings("unchecked") E object =
> (E)objectInputStream.readObject();
> data.add(validate(i, object));
> didAdd(i, object);
> }finally{
> notificationLock.unlock();
> }
> }
> }
> }
>
> /**
> * Returns a shallow copy of this list.
> * @return a shallow copy of this list.
> */
> @Override
> public Object clone() {
> SynchronizedCopyOnWriteEList<E> clone = new
> SynchronizedCopyOnWriteEList<E>();
> clone.data.addAll(this.data);
> return clone;
> }
>
> /**
> * An extensible iterator implementation.
> * @deprecated * @see AbstractEList.EIterator
> */
> @Deprecated
> protected class EIterator<E1> extends AbstractEList<E>.EIterator<E1>
> {
> // Pointless extension
> }
>
> /**
> * An extended read-only iterator that does not {@link
> BasicEList#resolve resolve} objects.
> * @deprecated
> * @see AbstractEList.NonResolvingEIterator
> */
> @Deprecated
> protected class NonResolvingEIterator<E1> extends
> AbstractEList<E>.NonResolvingEIterator<E1>
> {
> // Pointless extension
> }
>
> /**
> * An extensible list iterator implementation.
> * @deprecated
> * @see AbstractEList.EListIterator
> */
> @Deprecated
> protected class EListIterator<E1> extends
> AbstractEList<E>.EListIterator<E1>
> {
> /**
> * Creates an instance.
> */
> public EListIterator() {
> super();
> }
>
> /**
> * Creates an instance advanced to the index.
> * @param index the starting index.
> */
> public EListIterator(int index) {
> super(index);
> cursor = index;
> }
> }
>
> /**
> * An extended read-only list iterator that does not {@link
> BasicEList#resolve resolve} objects.
> * @deprecated
> * @see AbstractEList.NonResolvingEListIterator
> */
> @Deprecated
> protected class NonResolvingEListIterator<E1> extends
> AbstractEList<E>.NonResolvingEListIterator<E1>
> {
> /**
> * Creates an instance.
> */
> public NonResolvingEListIterator()
> {
> super();
> }
>
> /**
> * Creates an instance advanced to the index.
> * @param index the starting index.
> */
> public NonResolvingEListIterator(int index) {
> super(index);
> }
> }
>
> /**
> * An unmodifiable version of {@link BasicEList}.
> */
> public static class UnmodifiableEList<E> extends
> SynchronizedCopyOnWriteEList<E>
> {
> private static final long serialVersionUID = 1L;
>
> /**
> * Creates an initialized instance.
> * @param size the size of the list.
> * @param data the underlying storage of the list.
> */
> public UnmodifiableEList(Object [] data) {
> this.data.addAll(Arrays.asList(data));
> }
>
> /**
> * Throws an exception.
> * @exception UnsupportedOperationException always because
> it's not supported.
> */
> @Override
> public E set(int index, E object) {
> throw new UnsupportedOperationException();
> }
>
> /**
> * Throws an exception.
> * @exception UnsupportedOperationException always because
> it's not supported.
> */
> @Override
> public boolean add(E object) {
> throw new UnsupportedOperationException();
> }
>
> /**
> * Throws an exception.
> * @exception UnsupportedOperationException always because
> it's not supported.
> */
> @Override
> public void add(int index, E object) {
> throw new UnsupportedOperationException();
> }
>
> /**
> * Throws an exception.
> * @exception UnsupportedOperationException always because
> it's not supported.
> */
> @Override
> public boolean addAll(Collection<? extends E> collection)
> {
> throw new UnsupportedOperationException();
> }
>
> /**
> * Throws an exception.
> * @exception UnsupportedOperationException always because
> it's not supported.
> */
> @Override
> public boolean addAll(int index, Collection<? extends E>
> collection) {
> throw new UnsupportedOperationException();
> }
>
> /**
> * Throws an exception.
> * @exception UnsupportedOperationException always because
> it's not supported.
> */
> @Override
> public boolean remove(Object object) {
> throw new UnsupportedOperationException();
> }
>
> /**
> * Throws an exception.
> * @exception UnsupportedOperationException always because
> it's not supported.
> */
> @Override
> public E remove(int index) {
> throw new UnsupportedOperationException();
> }
>
> /**
> * Throws an exception.
> * @exception UnsupportedOperationException always because
> it's not supported.
> */
> @Override
> public boolean removeAll(Collection<?> collection) {
> throw new UnsupportedOperationException();
> }
>
> /**
> * Throws an exception.
> * @exception UnsupportedOperationException always because
> it's not supported.
> */
> @Override
> public boolean retainAll(Collection<?> collection) {
> throw new UnsupportedOperationException();
> }
>
> /**
> * Throws an exception.
> * @exception UnsupportedOperationException always because
> it's not supported.
> */
> @Override
> public void clear() {
> throw new UnsupportedOperationException();
> }
>
> /**
> * Throws an exception.
> * @exception UnsupportedOperationException always because
> it's not supported.
> */
> @Override
> public void move(int index, E object) {
> throw new UnsupportedOperationException();
> }
>
> /**
> * Throws an exception.
> * @exception UnsupportedOperationException always because
> it's not supported.
> */
> @Override
> public E move(int targetIndex, int sourceIndex)
> {
> throw new UnsupportedOperationException();
> }
>
> /**
> * Throws an exception.
> * @exception UnsupportedOperationException always because
> it's not supported.
> */
> @Override
> public void shrink() {
> throw new UnsupportedOperationException();
> }
>
> /**
> * Throws an exception.
> * @exception UnsupportedOperationException always because
> it's not supported.
> */
> @Override
> public void grow(int minimumCapacity) {
> throw new UnsupportedOperationException();
> }
>
> /**
> * Returns the {@link BasicEList#basicIterator basic iterator}.
> * @return the basic iterator.
> */
> @Override
> public Iterator<E> iterator() {
> return basicIterator();
> }
>
> /**
> * Returns the {@link #basicListIterator() basic list iterator}.
> * @return the basic list iterator.
> */
> @Override
> public ListIterator<E> listIterator() {
> return basicListIterator();
> }
>
> /**
> * Returns the {@link #basicListIterator(int) basic list
> iterator} advanced to the index.
> * @param index the starting index.
> * @return the basic list iterator.
> */
> @Override
> public ListIterator<E> listIterator(int index) {
> return basicListIterator(index);
> }
> }
>
> /**
> * Returns an <b>unsafe</b> list that provides a {@link #resolve
> non-resolving} view of the underlying data storage.
> * @return an <b>unsafe</b> list that provides a non-resolving
> view of the underlying data storage.
> */
> @Override
> protected List<E> basicList()
> {
> if (size == 0)
> {
> return ECollections.emptyEList();
> }
> else
> {
> return new
> SynchronizedCopyOnWriteEList.UnmodifiableEList<E>(data.toArray());
> }
> }
>
> /**
> * A <code>BasicEList</code> that {@link #useEquals uses}
> <code>==</code> instead of <code>equals</code> to compare members.
> */
> public static class FastCompare<E> extends BasicEList<E>
> {
> private static final long serialVersionUID = 1L;
>
> /**
> * Creates an empty instance with no initial capacity.
> */
> public FastCompare()
> {
> super();
> }
>
> /**
> * Creates an empty instance with the given capacity.
> * @param initialCapacity the initial capacity of the list
> before it must grow.
> * @exception IllegalArgumentException if the
> <code>initialCapacity</code> is negative.
> */
> public FastCompare(int initialCapacity)
> {
> super(initialCapacity);
> }
>
> /**
> * Creates an instance that is a copy of the collection.
> * @param collection the initial contents of the list.
> */
> public FastCompare(Collection<? extends E> collection)
> {
> super(collection.size());
> addAll(collection);
> }
>
> /**
> * Returns <code>false</code> because this list uses
> <code>==</code>.
> * @return <code>false</code>.
> */
> @Override
> protected boolean useEquals()
> {
> return false;
> }
> }
>
> /**
> * Returns an iterator.
> * This implementation allocates a {@link AbstractEList.EIterator}.
> * @return an iterator.
> * @see AbstractEList.EIterator
> */
> @Override
> public Iterator<E> iterator()
> {
> return new SynchronizedCopyOnWriteEIterator();
> }
>
> /**
> * An extensible iterator implementation.
> */
> protected class SynchronizedCopyOnWriteEIterator implements
> Iterator<E>
> {
> int index = -1;
> Iterator<Object> internalIterator = data.iterator();
> @Override
> public boolean hasNext() {
> return internalIterator.hasNext();
> }
>
> @SuppressWarnings("unchecked")
> @Override
> public E next() {
> index++;
> E object = (E)internalIterator.next();
> return resolve(index, object);
> }
>
> @Override
> public void remove() {
> internalIterator.remove();
> }
> }
>
> /**
> * Returns a read-only iterator that does not {@link #resolve
> resolve} objects.
> * This implementation allocates a {@link NonResolvingEIterator}.
> * @return a read-only iterator that does not resolve objects.
> */
> protected Iterator<E> basicIterator()
> {
> return new NonResolvingSynchronizedCopyOnWriteEIterator();
> }
>
> /**
> * An extended read-only iterator that does not {@link
> AbstractEList#resolve resolve} objects.
> */
> protected class NonResolvingSynchronizedCopyOnWriteEIterator
> extends SynchronizedCopyOnWriteEIterator
> {
> @SuppressWarnings("unchecked")
> @Override
> public E next() {
> index++;
> return (E)internalIterator.next();
> }
>
> /**
> * Throws and exception.
> * @exception UnsupportedOperationException always because
> it's not supported.
> */
> @Override
> public void remove()
> {
> throw new UnsupportedOperationException();
> }
> }
> }
>
>
>
Re: Copy On Write EList [message #1072234 is a reply to message #1072162] Mon, 22 July 2013 07:04 Go to previous messageGo to next message
Eclipse UserFriend
Ed Merks wrote on Mon, 22 July 2013 09:56
Xavipen,

Comments below.

On 21/07/2013 9:54 PM, Xavipen Mising name wrote:
> Hi! I know it has been a long time, but i would like to leave here my
> expirience on creating a threadSafe Model.
>
> First, no proxy are use in this model. So there may be other side
> effects for that.
>
> The first for the Elist was to modifiy the templates to create
> wrappers for the Elist and return the wrappers instead of the Elist.
> This solution was suggested here, and it works, with the side effect
> of deadlocks (but when dealing with multithreading is already a
> posibility). Unfortunatelly i did not have the time and i did not have
> to time to improve this solution by looking into DelegatingEcoreEList
> (project time constrains as usual) and i would not know were to start
> ( Some remarks would be aprreceated in case i have the time to revisit
> this).
In general, it seems to me not possible to make a multi-threaded
application well behaved simply by making primitives thread safe. E.g.,
two threads operating on the opposite ends of a bidirectional reference
will tend to deadlock. Do the wrappers implement InternalEList and
EStructuralFeature.Setting?


The Wrappers contain a reference to the internal EList implementation and a reference to the synchronization mechnism used. All calls in the wrapper are pass through the synchronization mechnism (it is different the way it is done between Locks and Actors) We have not defined bidirectional references, so i would have to have a look, but any reflective method needed could be overwritten in the template of code generation to ensure it is also called through the synchornization mechnism. In the case of Actors if the function does not return a value the call is asynchronous and no lock can happend.

For iterators on the Elist that is a bit more unclear how to deal with them. The Elist should be only handle with in the clas containing it. Only the GUI may want to iteract with the EList in such a manner. At the moment and till a time for a better implementation comes. The GUI (Parts) will do a copy of the EList and work with it and listen for the changes, so we avoid do iterations over the Elist. At least till i have time to understand better the EMF databinding and the Edit Framework.


Quote:


Two threads both doing an addAll at some index will not generally
produce atomic results because none of the addAll methods lock over the
whole operation.


I do not understand what you exactely mean with the addAll not being atomic.
I believe with the wrappers it will as the whole call to addAll is wrapped.

However i would like to have a bit more information in a clean way of delegating and implementation of an EList, in case i have time of working in an implementation.


Quote:

>
> The template generation also add thread safe by either using locks or
> Actors. This code and the EList wrappers are activated by an
> annotation in the model.
>
> The next issue was the Adapters List in the EObject, for this I
> replace the implementation of the EAdapterList with my implementation
> (included at the end of the post, no guaratees but it is what we used
> with EMF 2.7.0).
This is an example of what I mean. Often an adapter factory will check
if an adapter is already present and then add it if not. Even if the
list is thread safe, the adapter can end up being added twice...


I have just started to look at the Edit Framework, so i am in a better position now to understand the issue you mention now.

It seems to me that there is an easy solution for this particular issue. Ensure Uniqueness in the Adapter list will solved having an adapter twice. Thanks for pointing this out. I will add that to the implementation as soon as i can.

Quote:

>
> Then some problems appear in the Edit Framework with the notifiers, so
> the solution was to replace the ChangeNotifier Implementation by a
> ConcurrentChangeNotifier and a ConcurrentComposedAdapterFactory with
> use a CopyOnWriteArraList to store the listeners.
>
>
> Cheers,
>
>

Re: Copy On Write EList [message #1072246 is a reply to message #1072234] Mon, 22 July 2013 07:27 Go to previous messageGo to next message
Eclipse UserFriend
Xavipen,

Comments below.

On 22/07/2013 1:04 PM, Xavipen Mising name wrote:
> Ed Merks wrote on Mon, 22 July 2013 09:56
>> Xavipen,
>>
>> Comments below.
>>
>> On 21/07/2013 9:54 PM, Xavipen Mising name wrote:
>> > Hi! I know it has been a long time, but i would like to leave here
>> my > expirience on creating a threadSafe Model.
>> >
>> > First, no proxy are use in this model. So there may be other side >
>> effects for that.
>> >
>> > The first for the Elist was to modifiy the templates to create >
>> wrappers for the Elist and return the wrappers instead of the Elist.
>> > This solution was suggested here, and it works, with the side
>> effect > of deadlocks (but when dealing with multithreading is
>> already a > posibility). Unfortunatelly i did not have the time and i
>> did not have > to time to improve this solution by looking into
>> DelegatingEcoreEList > (project time constrains as usual) and i would
>> not know were to start > ( Some remarks would be aprreceated in case
>> i have the time to revisit > this).
>> In general, it seems to me not possible to make a multi-threaded
>> application well behaved simply by making primitives thread safe.
>> E.g., two threads operating on the opposite ends of a bidirectional
>> reference will tend to deadlock. Do the wrappers implement
>> InternalEList and EStructuralFeature.Setting?
>
>
> The Wrappers contain a reference to the internal EList implementation
> and a reference to the synchronization mechnism used. All calls in the
> wrapper are pass through the synchronization mechnism (it is different
> the way it is done between Locks and Actors) We have not defined
> bidirectional references, so i would have to have a look, but any
> reflective method needed could be overwritten in the template of code
> generation to ensure it is also called through the synchornization
> mechnism. In the case of Actors if the function does not return a
> value the call is asynchronous and no lock can happend.
The point is the EList returned for a multi-valued feature is assumed to
implement InternalEList and EStructuralFeature.Setting, so I imagined
that wrappers would need to implement those interfaces too, not just EList.
>
> For iterators on the Elist that is a bit more unclear how to deal with
> them. The Elist should be only handle with in the clas containing it.
> Only the GUI may want to iteract with the EList in such a manner. At
> the moment and till a time for a better implementation comes. The GUI
> (Parts) will do a copy of the EList and work with it and listen for
> the changes, so we avoid do iterations over the Elist. At least till i
> have time to understand better the EMF databinding and the Edit
> Framework.
Of course it's very common to iterate over lists throughout the
framework and that's pretty much impossible to make thread safe without
a look being held during the entire processing done with the iterator...
>
>
> Quote:
>> Two threads both doing an addAll at some index will not generally
>> produce atomic results because none of the addAll methods lock over
>> the whole operation.
>
>
> I do not understand what you exactely mean with the addAll not being
> atomic.
> I believe with the wrappers it will as the whole call to addAll is
> wrapped.

I don't see any locking in this method:

public boolean addAllUnique(Collection<? extends E> collection) {

for ( E object: collection)
{
addUnique(object);
}

return collection.size() != 0;
}
>
> However i would like to have a bit more information in a clean way of
> delegating and implementation of an EList, in case i have time of
> working in an implementation.
As I mentioned already, I don't think it's possible to achieve proper
threaded application behavior just by making "primitives" thread safe.
For example, the following code isn't thread safe even if the list
itself is thread safe.

int index = list.indexOf(o);
if (index != -1)
{
list.remove(index);
}

That's because anything could modify the list between when indexOf is
called and when remove is called.
>
>
> Quote:
>> >
>> > The template generation also add thread safe by either using locks
>> or > Actors. This code and the EList wrappers are activated by an >
>> annotation in the model.
>> >
>> > The next issue was the Adapters List in the EObject, for this I >
>> replace the implementation of the EAdapterList with my implementation
>> > (included at the end of the post, no guaratees but it is what we
>> used > with EMF 2.7.0).
>> This is an example of what I mean. Often an adapter factory will
>> check if an adapter is already present and then add it if not. Even
>> if the list is thread safe, the adapter can end up being added twice...
>
>
> I have just started to look at the Edit Framework, so i am in a better
> position now to understand the issue you mention now.
>
> It seems to me that there is an easy solution for this particular
> issue. Ensure Uniqueness in the Adapter list will solved having an
> adapter twice. Thanks for pointing this out. I will add that to the
> implementation as soon as i can.
In each case, it again boils down to making something behave atomically,
but as in the example I show above, some higher level logic will
nevertheless make assumptions about consistency between when the model
is read and when it's modified base on the information determined while
reading the model. In each case, the locking behavior needs to be moved
up the encompass of the higher level logic, e.g., hold a lock during the
entire lifetime of an iterator.

As such, it generally boils down to the application having a high level
locking/threading policy and when that is present, the fine grained
locks on the "primitives" serve no purpose other than to interfere by
giving rise to deadlocks.
> Quote:
>> >
>> > Then some problems appear in the Edit Framework with the notifiers,
>> so > the solution was to replace the ChangeNotifier Implementation by
>> a > ConcurrentChangeNotifier and a ConcurrentComposedAdapterFactory
>> with > use a CopyOnWriteArraList to store the listeners.
>> >
>> >
>> > Cheers,
>> >
>> >
>
>
Re: Copy On Write EList [message #1072270 is a reply to message #1072246] Mon, 22 July 2013 08:33 Go to previous messageGo to next message
Eclipse UserFriend
Ed Merks wrote on Mon, 22 July 2013 13:27
Xavipen,

Comments below.

On 22/07/2013 1:04 PM, Xavipen Mising name wrote:
> Ed Merks wrote on Mon, 22 July 2013 09:56
>> Xavipen,
>>
>> Comments below.
>>
>> On 21/07/2013 9:54 PM, Xavipen Mising name wrote:
>> > Hi! I know it has been a long time, but i would like to leave here
>> my > expirience on creating a threadSafe Model.
>> >
>> > First, no proxy are use in this model. So there may be other side >
>> effects for that.
>> >
>> > The first for the Elist was to modifiy the templates to create >
>> wrappers for the Elist and return the wrappers instead of the Elist.
>> > This solution was suggested here, and it works, with the side
>> effect > of deadlocks (but when dealing with multithreading is
>> already a > posibility). Unfortunatelly i did not have the time and i
>> did not have > to time to improve this solution by looking into
>> DelegatingEcoreEList > (project time constrains as usual) and i would
>> not know were to start > ( Some remarks would be aprreceated in case
>> i have the time to revisit > this).
>> In general, it seems to me not possible to make a multi-threaded
>> application well behaved simply by making primitives thread safe.
>> E.g., two threads operating on the opposite ends of a bidirectional
>> reference will tend to deadlock. Do the wrappers implement
>> InternalEList and EStructuralFeature.Setting?
>
>
> The Wrappers contain a reference to the internal EList implementation
> and a reference to the synchronization mechnism used. All calls in the
> wrapper are pass through the synchronization mechnism (it is different
> the way it is done between Locks and Actors) We have not defined
> bidirectional references, so i would have to have a look, but any
> reflective method needed could be overwritten in the template of code
> generation to ensure it is also called through the synchornization
> mechnism. In the case of Actors if the function does not return a
> value the call is asynchronous and no lock can happend.
The point is the EList returned for a multi-valued feature is assumed to
implement InternalEList and EStructuralFeature.Setting, so I imagined
that wrappers would need to implement those interfaces too, not just EList.


The Wrappers implement InternalEList. I will need to have a look a EStructuralFeature.Setting interface.


Quote:

>
> For iterators on the Elist that is a bit more unclear how to deal with
> them. The Elist should be only handle with in the clas containing it.
> Only the GUI may want to iteract with the EList in such a manner. At
> the moment and till a time for a better implementation comes. The GUI
> (Parts) will do a copy of the EList and work with it and listen for
> the changes, so we avoid do iterations over the Elist. At least till i
> have time to understand better the EMF databinding and the Edit
> Framework.
Of course it's very common to iterate over lists throughout the
framework and that's pretty much impossible to make thread safe without
a look being held during the entire processing done with the iterator...


Besides the EMF databinding and Edit Framework. The GUI code will not access the list directly. So, it may be possible to find an Iterator implmentation for that concrete problem. I comment more on this below. I agree that is impossible to find a global solution.

Quote:

>
>
> Quote:
>> Two threads both doing an addAll at some index will not generally
>> produce atomic results because none of the addAll methods lock over
>> the whole operation.
>
>
> I do not understand what you exactely mean with the addAll not being
> atomic.
> I believe with the wrappers it will as the whole call to addAll is
> wrapped.

I don't see any locking in this method:

public boolean addAllUnique(Collection<? extends E> collection) {

for ( E object: collection)
{
addUnique(object);
}

return collection.size() != 0;
}


That would be the implementation of the wrapper using locks.

@Override
public boolean addAll(int index, Collection<? extends E> c) {
dataLock.lock();
try{
return list.addAll(index, c);
}finally{
dataLock.unlock();
}
}
Quote:


>
> However i would like to have a bit more information in a clean way of
> delegating and implementation of an EList, in case i have time of
> working in an implementation.
As I mentioned already, I don't think it's possible to achieve proper
threaded application behavior just by making "primitives" thread safe.
For example, the following code isn't thread safe even if the list
itself is thread safe.

int index = list.indexOf(o);
if (index != -1)
{
list.remove(index);
}

That's because anything could modify the list between when indexOf is
called and when remove is called.
>
>
> Quote:
>> >
>> > The template generation also add thread safe by either using locks
>> or > Actors. This code and the EList wrappers are activated by an >
>> annotation in the model.
>> >
>> > The next issue was the Adapters List in the EObject, for this I >
>> replace the implementation of the EAdapterList with my implementation
>> > (included at the end of the post, no guaratees but it is what we
>> used > with EMF 2.7.0).
>> This is an example of what I mean. Often an adapter factory will
>> check if an adapter is already present and then add it if not. Even
>> if the list is thread safe, the adapter can end up being added twice...
>
>
> I have just started to look at the Edit Framework, so i am in a better
> position now to understand the issue you mention now.
>
> It seems to me that there is an easy solution for this particular
> issue. Ensure Uniqueness in the Adapter list will solved having an
> adapter twice. Thanks for pointing this out. I will add that to the
> implementation as soon as i can.
In each case, it again boils down to making something behave atomically,
but as in the example I show above, some higher level logic will
nevertheless make assumptions about consistency between when the model
is read and when it's modified base on the information determined while
reading the model. In each case, the locking behavior needs to be moved
up the encompass of the higher level logic, e.g., hold a lock during the
entire lifetime of an iterator.

As such, it generally boils down to the application having a high level
locking/threading policy and when that is present, the fine grained
locks on the "primitives" serve no purpose other than to interfere by
giving rise to deadlocks.



That is correct. That is why in our model the EClass that have the list as a feature
is the only code that works with the list an can modify it. For operations on the list a method needs to be added to the EClass. The GUI can use then commands to call those methods when it needs to modify the list, but it should never work on it on its own. This is what we can control in the design a implementation of our model.

The only other code that therefore should access the list directly are EMF databindings and Edit framework. How are they doing so is what i am trying to find out, to see if i can deal with it.

For example, if the iterations are mainly trigger by listening to change notification then it should be enough that the iterator contains an inmmutable copy of the list. If while iterating over the list the list is modified then a new notification would be received and processed afterwards, so the GUI will display the changes.

It is clear that some constrains will have to be used when writting the GUI code and dealing with the lists. For example it commens to mind: generic commands using the reflective methods are not to be used.

In the way we use the generated model, the whole logic and changes to the list should be inside the EClass code. I realise that this approach is not for everyone but only for the concrete problem we are trying to solve. It also means that some nice capabilities of the EMF Framework would not be used to ensure the Thread Safety.

This is what i am trying to assets currently.

Quote:

> Quote:
>> >
>> > Then some problems appear in the Edit Framework with the notifiers,
>> so > the solution was to replace the ChangeNotifier Implementation by
>> a > ConcurrentChangeNotifier and a ConcurrentComposedAdapterFactory
>> with > use a CopyOnWriteArraList to store the listeners.
>> >
>> >
>> > Cheers,
>> >
>> >
>
>

Re: Copy On Write EList [message #1072280 is a reply to message #1072270] Mon, 22 July 2013 08:53 Go to previous message
Eclipse UserFriend
Javi,

Comments below.

On 22/07/2013 2:33 PM, Xavipen Mising name wrote:
> Ed Merks wrote on Mon, 22 July 2013 13:27
>> Xavipen,
>>
>> Comments below.
>>
>> On 22/07/2013 1:04 PM, Xavipen Mising name wrote:
>> > Ed Merks wrote on Mon, 22 July 2013 09:56
>> >> Xavipen,
>> >>
>> >> Comments below.
>> >>
>> >> On 21/07/2013 9:54 PM, Xavipen Mising name wrote:
>> >> > Hi! I know it has been a long time, but i would like to leave
>> here >> my > expirience on creating a threadSafe Model.
>> >> >
>> >> > First, no proxy are use in this model. So there may be other
>> side > >> effects for that.
>> >> >
>> >> > The first for the Elist was to modifiy the templates to create >
>> >> wrappers for the Elist and return the wrappers instead of the
>> Elist. >> > This solution was suggested here, and it works, with the
>> side >> effect > of deadlocks (but when dealing with multithreading
>> is >> already a > posibility). Unfortunatelly i did not have the time
>> and i >> did not have > to time to improve this solution by looking
>> into >> DelegatingEcoreEList > (project time constrains as usual) and
>> i would >> not know were to start > ( Some remarks would be
>> aprreceated in case >> i have the time to revisit > this).
>> >> In general, it seems to me not possible to make a multi-threaded
>> >> application well behaved simply by making primitives thread safe.
>> >> E.g., two threads operating on the opposite ends of a
>> bidirectional >> reference will tend to deadlock. Do the wrappers
>> implement >> InternalEList and EStructuralFeature.Setting?
>> >
>> >
>> > The Wrappers contain a reference to the internal EList
>> implementation > and a reference to the synchronization mechnism
>> used. All calls in the > wrapper are pass through the synchronization
>> mechnism (it is different > the way it is done between Locks and
>> Actors) We have not defined > bidirectional references, so i would
>> have to have a look, but any > reflective method needed could be
>> overwritten in the template of code > generation to ensure it is also
>> called through the synchornization > mechnism. In the case of Actors
>> if the function does not return a > value the call is asynchronous
>> and no lock can happend.
>> The point is the EList returned for a multi-valued feature is assumed
>> to implement InternalEList and EStructuralFeature.Setting, so I
>> imagined that wrappers would need to implement those interfaces too,
>> not just EList.
>
>
> The Wrappers implement InternalEList. I will need to have a look a
> EStructuralFeature.Setting interface.
That being missing will be something you notice if you use any of the
cross referencers (e.g., when using the DeleteCommand).
>
>
> Quote:
>> >
>> > For iterators on the Elist that is a bit more unclear how to deal
>> with > them. The Elist should be only handle with in the clas
>> containing it. > Only the GUI may want to iteract with the EList in
>> such a manner. At > the moment and till a time for a better
>> implementation comes. The GUI > (Parts) will do a copy of the EList
>> and work with it and listen for > the changes, so we avoid do
>> iterations over the Elist. At least till i > have time to understand
>> better the EMF databinding and the Edit > Framework.
>> Of course it's very common to iterate over lists throughout the
>> framework and that's pretty much impossible to make thread safe
>> without a look being held during the entire processing done with the
>> iterator...
>
>
> Besides the EMF databinding and Edit Framework. The GUI code will not
> access the list directly. So, it may be possible to find an Iterator
> implmentation for that concrete problem. I comment more on this below.
> I agree that is impossible to find a global solution.
>
> Quote:
>> >
>> >
>> > Quote:
>> >> Two threads both doing an addAll at some index will not generally
>> >> produce atomic results because none of the addAll methods lock
>> over >> the whole operation.
>> >
>> >
>> > I do not understand what you exactely mean with the addAll not
>> being > atomic.
>> > I believe with the wrappers it will as the whole call to addAll is
>> > wrapped.
>>
>> I don't see any locking in this method:
>>
>> public boolean addAllUnique(Collection<? extends E>
>> collection) {
>>
>> for ( E object: collection)
>> {
>> addUnique(object);
>> }
>>
>> return collection.size() != 0;
>> }
>
>
> That would be the implementation of the wrapper using locks.
>
> @Override
> public boolean addAll(int index, Collection<? extends E> c) {
> dataLock.lock();
> try{
> return list.addAll(index, c);
> }finally{
> dataLock.unlock();
> }
> }
> Quote:
>> >
>> > However i would like to have a bit more information in a clean way
>> of > delegating and implementation of an EList, in case i have time
>> of > working in an implementation.
>> As I mentioned already, I don't think it's possible to achieve proper
>> threaded application behavior just by making "primitives" thread
>> safe. For example, the following code isn't thread safe even if the
>> list itself is thread safe.
>>
>> int index = list.indexOf(o);
>> if (index != -1)
>> {
>> list.remove(index);
>> }
>>
>> That's because anything could modify the list between when indexOf is
>> called and when remove is called.
>> >
>> >
>> > Quote:
>> >> >
>> >> > The template generation also add thread safe by either using
>> locks >> or > Actors. This code and the EList wrappers are activated
>> by an > >> annotation in the model.
>> >> >
>> >> > The next issue was the Adapters List in the EObject, for this I
>> > >> replace the implementation of the EAdapterList with my
>> implementation >> > (included at the end of the post, no guaratees
>> but it is what we >> used > with EMF 2.7.0).
>> >> This is an example of what I mean. Often an adapter factory will
>> >> check if an adapter is already present and then add it if not.
>> Even >> if the list is thread safe, the adapter can end up being
>> added twice...
>> >
>> >
>> > I have just started to look at the Edit Framework, so i am in a
>> better > position now to understand the issue you mention now.
>> >
>> > It seems to me that there is an easy solution for this particular >
>> issue. Ensure Uniqueness in the Adapter list will solved having an >
>> adapter twice. Thanks for pointing this out. I will add that to the >
>> implementation as soon as i can.
>> In each case, it again boils down to making something behave
>> atomically, but as in the example I show above, some higher level
>> logic will nevertheless make assumptions about consistency between
>> when the model is read and when it's modified base on the information
>> determined while reading the model. In each case, the locking
>> behavior needs to be moved up the encompass of the higher level
>> logic, e.g., hold a lock during the entire lifetime of an iterator.
>>
>> As such, it generally boils down to the application having a high
>> level locking/threading policy and when that is present, the fine
>> grained locks on the "primitives" serve no purpose other than to
>> interfere by giving rise to deadlocks.
>
>
> That is correct. That is why in our model the EClass that have the
> list as a feature
> is the only code that works with the list an can modify it. For
> operations on the list a method needs to be added to the EClass. The
> GUI can use then commands to call those methods when it needs to
> modify the list, but it should never work on it on its own. This is
> what we can control in the design a implementation of our model.
I see.
>
> The only other code that therefore should access the list directly are
> EMF databindings and Edit framework. How are they doing so is what i
> am trying to find out, to see if i can deal with it.
>
> For example, if the iterations are mainly trigger by listening to
> change notification then it should be enough that the iterator
> contains an inmmutable copy of the list. If while iterating over the
> list the list is modified then a new notification would be received
> and processed afterwards, so the GUI will display the changes.
> It is clear that some constrains will have to be used when writting
> the GUI code and dealing with the lists. For example it commens to
> mind: generic commands using the reflective methods are not to be used.
> In the way we use the generated model, the whole logic and changes to
> the list should be inside the EClass code. I realise that this
> approach is not for everyone but only for the concrete problem we are
> trying to solve. It also means that some nice capabilities of the EMF
> Framework would not be used to ensure the Thread Safety.
> This is what i am trying to assets currently.
>
> Quote:
>> > Quote:
>> >> >
>> >> > Then some problems appear in the Edit Framework with the
>> notifiers, >> so > the solution was to replace the ChangeNotifier
>> Implementation by >> a > ConcurrentChangeNotifier and a
>> ConcurrentComposedAdapterFactory >> with > use a CopyOnWriteArraList
>> to store the listeners.
>> >> >
>> >> >
>> >> > Cheers,
>> >> >
>> >> >
>> >
>> >
>
>
Previous Topic:Derived attributes, stored in feature map (for anyAttribute support)
Next Topic:[CDO] Databinding - ObservableListTreeContentProvider problem with removes from a list in a CDOView
Goto Forum:
  


Current Time: Wed Jul 30 12:09:18 EDT 2025

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

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

Back to the top