Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 8de8d5ae07f0c52be478afbeba4e6474c5c7735f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
/*******************************************************************************
 * Copyright (c) 2008 Wind River Systems and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     Wind River Systems - initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.dsf.concurrent;

import org.eclipse.core.runtime.CoreException;

/**
 * @since 2.2
 */
public abstract class Transaction<V> {

	/**
	 * The exception we throw when the client transaction logic asks us to
	 * validate a cache object that is stale (or has never obtained a value from
	 * the source)
	 */
    private static final InvalidCacheException INVALID_CACHE_EXCEPTION = new InvalidCacheException();
    
	/** The request object we've been given to set the transaction results in */
    private DataRequestMonitor<V> fRm;
    
    public static class InvalidCacheException extends Exception {
        private static final long serialVersionUID = 1L;
    }

	/**
	 * Kicks off the transaction. We'll either complete the request monitor
	 * immediately if all the data points the transaction needs are cached and
	 * valid, or we'll end up asynchronously completing the monitor if and when
	 * either (a) all the data points are available and up-to-date, or (b)
	 * obtaining them from the source encountered an error. Note that there is
	 * potential in (b) for us to never complete the monitor. If one or more
	 * data points are perpetually becoming stale, then we'll indefinitely wait
	 * for them to stabilize. The caller should cancel its request monitor in
	 * order to get us to stop waiting.
	 * 
	 * @param rm Request completion monitor.
	 */
    public void request(DataRequestMonitor<V> rm) {
        if (fRm != null) {
            assert fRm.isCanceled();
            fRm.done();
        }
        fRm = rm;
        execute();
    }

	/**
	 * The transaction logic--code that tries to synchronously make use of,
	 * usually, multiple data points that are normally obtained asynchronously.
	 * Each data point is represented by a cache object. The transaction logic
	 * must check the validity of each cache object just prior to using it
	 * (calling its getData()). It should do that check by calling one of our
	 * validate() methods. Those methods will throw InvalidCacheException if the
	 * cached data is invalid (stale, e.g.,) or CoreException if an error was
	 * encountered the last time it got data form the source. The exception will
	 * abort the transaction, but in the case of InvalidCacheException, we
	 * schedule an asynchronous call that will re-invoke the transaction
	 * logic once the cache object has been updated from the source.
	 * 
	 * @return the cached data if it's valid, otherwise an exception is thrown
	 * @throws InvalidCacheException
	 * @throws CoreException
	 */
    abstract protected V process() throws InvalidCacheException, CoreException;

	/**
	 * Method which invokes the transaction logic and handles any exception that
	 * may result. If that logic encounters a stale/unset cache object, then we
	 * simply do nothing. This method will be called again once the cache
	 * objects tell us it has obtained an updated value form the source.
	 */
    private void execute() {
        if (fRm.isCanceled()) {
            fRm.done();
            fRm = null;
            return;
        }
        
        try {
        	// Execute the transaction logic
            V data = process();
            
			// No exception means all cache objects used by the transaction
			// were valid and up to date. Complete the request
            fRm.setData(data);
            fRm.done();
            fRm = null;
        }
        catch (CoreException e) {
			// At least one of the cache objects encountered a failure obtaining
			// the data from the source. Complete the request.
            fRm.setStatus(e.getStatus());
            fRm.done();
            fRm = null;
        }
        catch (InvalidCacheException e) {
			// At least one of the cache objects was stale/unset. Keep the
			// request monitor in the incomplete state, thus leaving our client
			// "waiting" (asynchronously). We'll get called again once the cache
			// objects are updated, thus re-starting the whole transaction
			// attempt.
        }
    }

	/**
	 * Clients must call one of our validate methods prior to using (calling
	 * getData()) on data cache object.
	 * 
	 * @param cache
	 *            the object being validated
	 * @throws InvalidCacheException
	 *             if the data is stale/unset
	 * @throws CoreException
	 *             if an error was encountered getting the data from the source
	 */
    protected void validate(RequestCache<?> cache) throws InvalidCacheException, CoreException {
        if (cache.isValid()) {
            if (!cache.getStatus().isOK()) {
                throw new CoreException(cache.getStatus());
            }
        } else {
			// Throw the invalid cache exception, but first schedule a
			// re-attempt of the transaction logic, to occur when the
			// stale/unset cache object has been updated
            cache.wait(new RequestMonitor(ImmediateExecutor.getInstance(), fRm) {
                @Override
                protected void handleCompleted() {
                    execute();
                }
            });
            throw INVALID_CACHE_EXCEPTION;
        }
    }

	/**
	 * See {@link #validate(RequestCache)}. This variant simply validates
	 * multiple cache objects.
	 */
    protected void validate(RequestCache<?> ... caches) throws InvalidCacheException, CoreException {
        // Check if any of the caches have errors:
        boolean allValid = true;
        
        for (RequestCache<?> cache : caches) {
            if (cache.isValid()) {
                if (!cache.getStatus().isOK()) {
                    throw new CoreException(cache.getStatus());
                }
            } else {
                allValid = false;
            }
        }
        if (!allValid) {
			// Throw the invalid cache exception, but first schedule a
			// re-attempt of the transaction logic, to occur when the
			// stale/unset cache objects have been updated
            CountingRequestMonitor countringRm = new CountingRequestMonitor(ImmediateExecutor.getInstance(), fRm) {
                @Override
                protected void handleCompleted() {
                    execute();
                }
            };
            int count = 0;
            for (RequestCache<?> cache : caches) {
                if (!cache.isValid()) {
                    cache.wait(countringRm);
                    count++;
                }
            }
            countringRm.setDoneCount(count);
            throw INVALID_CACHE_EXCEPTION;
        }        
    }

}

Back to the top