Hi!

Stefan Steiniger schrieb:
> Similar to Landon I have never touched threading nor heared any lectures 
> or read books on it. Thus .. i dont know if it turns out to be good or bad.
> But i know that making things more clean is good objective. Thus, i 
> support the changes. It would be good when we finally change the code, 
> that you also put into the code some docs or references where you 
> outline why somethings has changed. (actually I currently would opt to 
> leave the old code as comment and not to remove entirely)

The ThreadQueue is JavaDoc'ed but I can add some
design notes as well. Keeping old things as comments negates
the existence of version control systems.
CVS is exactly the time machine you for this.
As I pointed out earlier having a ChangeLog would
be nice to document motivations and backgrounds for a change. Simply
generating a ChangeLog out of the commit messages is convenient but
it does not uncover the true potential of such a 'log' file.

> Due to my lack of knowledge i forwarded your email to a colleague who 
> developed a web-service based on JUMP (which runs on a server, see [1]), 
> an ajax client and recently did some multiprocessor experiments for the 
> web-services processing.

I look forward to his comments.

> I hope 1) he will look on your proposal and 2) you can wait some days 
> until he, Larry and Michael got an opinion.

@Michael: You ask what to change. For the moment just replace
ThreadQueue.java with one I've sent last. The first patches
are addressing the problem with ThreadQueue limited to one
Thread at a time (defaultRenderingThreadQueue in RenderingManger).
You can apply these patches too, but they are not needed any
longer. With next patch I'll send the this second thread queue
will be removed from RenderingManager entirely.

What to test? Doing your day-to-day is the best thing
you can do. The most important thing is that the new one
behaves exactly like the old one. The magic word is compatibility.
I don't want to break the show. Use plug-ins, use many layers,
use less layer, use db and WMS layers. Do all the fancy stuff
you like to do. If the OJ freezes or something other looks odd
just tell me. :-)

- Sascha

> Michaël Michaud schrieb:
>> Sascha L. Teichmann a écrit :
>>
>>> Really back to topic:
>>>
>>> Find attached a replacement for ThreadQueue [1].
>>> To use it just overwrite the original one.
>>>  
>>>
>> Hi Sascha :
>>
>> I think that trying to have a cleaner and more simple code is an 
>> excellent goal, and I'd like to help, but I'm not sure I can understand 
>> all these thread issues.
>> If you tell me exactly which classes I must replace (on ly ThreadQueue 
>> or also the pieces of code from your previous mail) and what kind of 
>> tests I should do (rendering different kind of layers ? mixing different 
>> kind of layers), I'll try to make some more tests on my desktop.
>>
>> Thanks, for the hard work
>>
>> Michaël
>>
>>> This one works for the real parallel case of
>>> layer rendering too. Each time a thread finished
>>> executing its Runnable it looks into the queue
>>> if they are more jobs to do. This prevents unnecessary
>>> thread creations/shutdowns. If the queue is empty
>>> the worker thread is kept alive for 5 seconds waiting
>>> for new jobs. This results in a kind of thread pooling.
>>>
>>> @Larry: I've isolated my implementation and the OJ
>>> ThreadQueue and done a synthetic benchmark with a
>>> larger number of jobs (10,000+). My implementation
>>> works about two orders faster than the OJ one.
>>> But this is of little meaning because OJ only
>>> has to handle a number of jobs equal the number
>>> of layers. This will hardly hit 10,000+ ;-)
>>> But again: My mission is improve the structure not
>>> primarily the speed.
>>>
>>> I've tested the new ThreadQueue to some extent but
>>> I'am male(tm) after all ... potentially making mistakes.
>>> It would be very kind if someone test it too.
>>>
>>> My next step will be some clean up in the RenderingManager [2].
>>> I'am not sure that it is really needed to have two ThreadQueues
>>> there. The effect of the single tread one can be easily
>>> simulated with a data structure like the RunnableArrayList which
>>> I've posted already.
>>>
>>> Any comments?
>>>
>>> Yours,
>>>  Sascha
>>>
>>> [1] com.vividsolutions.jump.workbench.ui.renderer.ThreadQueue
>>> [2] com.vividsolutions.jump.workbench.ui.renderer.RenderingManager
>>>
>>> Sunburned Surveyor schrieb:
>>>  
>>>
>>>> Sascha,
>>>>
>>>> Please accept my sincerest aopologies. I'm afriad my American
>>>> ignorance of other cultures is more than just a little obvious at
>>>> times.
>>>>
>>>> I believe I have made the same mistake with Jan. :]
>>>>
>>>> Please be patient with me as I learn the details of cultures across
>>>> the Pacific and Atalantic Oceans!
>>>>
>>>> The Sunburned Surveyor
>>>>
>>>> On 5/24/07, Sascha L. Teichmann <[EMAIL PROTECTED]> wrote:
>>>>    
>>>>
>>>>> TNX, but for the records 'he' would be more suited in my case.
>>>>>
>>>>> 'Sascha' is basically a Russian term of endearment for the
>>>>> boys name 'Alexander' but it's also used as a girls name.
>>>>>
>>>>> BTW: 'Jan' is a girls name in the US too, isn't? ;-)
>>>>>
>>>>> - Sascha
>>>>>
>>>>>
>>>>> Sunburned Surveyor schrieb:
>>>>>      
>>>>>
>>>>>> Sascha and Larry,
>>>>>>
>>>>>> I must admit that I am way over my head here. I haven't done much
>>>>>> thread programming in Java. (Hopefully Stefan has!)
>>>>>>
>>>>>> Sascha wrote: "My primary goal is to simplify the
>>>>>> threading code to make it more reliable in terms of time."
>>>>>>
>>>>>> This seems like an admirable goal to me. If Larry, or a similar
>>>>>> programmer of his experience, agrees that this changes would be
>>>>>> beneficial, I say we give Sascha a shot at it. It sounds like she has
>>>>>> considered her changes carefully.
>>>>>>
>>>>>> Just my two cents.
>>>>>>
>>>>>> The Sunburned Surveyor
>>>>>>
>>>>>>
>>>>>>
>>>>>> On 5/24/07, Sascha L. Teichmann <[EMAIL PROTECTED]> wrote:
>>>>>>        
>>>>>>
>>>>>>> Hi Larry,
>>>>>>>
>>>>>>> short answer first: No, I don't have any benchmarks, yet.
>>>>>>>
>>>>>>> The long answer: My primary goal is to simplify the
>>>>>>> threading code to make it more reliable in terms of time.
>>>>>>> Gaining performance improvements would be a neat side effect.
>>>>>>>
>>>>>>> Background: Multi-threading may be fine for slow layers
>>>>>>> which arrives later on the screen but for exporting
>>>>>>> the data (to print/layout e.g) it would be nice to have
>>>>>>> them arriving one after the other.
>>>>>>>
>>>>>>> My final goal is to have a simple switch between the normal
>>>>>>> and the serial mode. To archive that I try to carefully
>>>>>>> refactor the system doing little patches step by step
>>>>>>> not to break it. Refactoring the ThreadQueue with it's
>>>>>>> 'flaws' seems a to be a good starting point to me.
>>>>>>>
>>>>>>> One reason for this change is the fact that you are able
>>>>>>> to figure out if the default thread runs empty. But there
>>>>>>> is no way to figure out when the last thread ends. That
>>>>>>> are different things. A mechanism for this is planned.
>>>>>>>
>>>>>>> Sorry, if I've shadowed my true intentions to much. Maybe
>>>>>>> I should discuss more before I send patches.  ;-)
>>>>>>>
>>>>>>> Back to the technical side:
>>>>>>>
>>>>>>> The patch needs some testing but I don't expect too much
>>>>>>> performance improvement.
>>>>>>>
>>>>>>> A less intrusive alternative to bind the Runnables that go to the
>>>>>>> default ThreadQueue into one thread is to create a container
>>>>>>> which self is Runnable (see e.g. RunnableArrayList attached)
>>>>>>> and put them into an instance of this class.
>>>>>>> This container is put into multiRendererThreadQueue as a Runnable.
>>>>>>> With this modification the defaultRendererThreadQueue can
>>>>>>> be removed (multiRendererThreadQueue renamed to
>>>>>>> defaultRendererThreadQueue). Only an idea ... I'm in discussion
>>>>>>> mode now. ;-)
>>>>>>>
>>>>>>> - Sascha
>>>>>>>
>>>>>>>
>>>>>>> Larry Becker schrieb:
>>>>>>>          
>>>>>>>
>>>>>>>> Hi Sascha,
>>>>>>>>
>>>>>>>>  I read your comments and look at your code with interest.  It appears
>>>>>>>> to be an improved ThreadQueue implementation, but will require a lot of
>>>>>>>> testing to verify.  Before I invest this time, I would like to know 
>>>>>>>> what
>>>>>>>> problem it is solving.  I see your dislikes a - e, but these are not
>>>>>>>> really problems, only architectural critiques.  Have you done any
>>>>>>>> benchmarks that show that the new SingleThreadQueue speeds up
>>>>>>>> rendering?  Your logical argument that it should be more efficient  is
>>>>>>>> persuasive,  but  I have been surprised by Java before.
>>>>>>>>
>>>>>>>> respectfully,
>>>>>>>> Larry Becker
>>>>>>>>
>>>>>>>> On 5/23/07, *Sascha L. Teichmann* <[EMAIL PROTECTED]
>>>>>>>> <mailto:[EMAIL PROTECTED]>> wrote:
>>>>>>>>
>>>>>>>>    Hi together,
>>>>>>>>
>>>>>>>>    as some of you may already know i have my dislikes against
>>>>>>>>    ThreadQueue [1] (Hi, Larry!) see my mail [2]
>>>>>>>>
>>>>>>>>    a - It forks a new thread for any Runnable it processes.
>>>>>>>>    b - It has an ugly busy wait loop inside.
>>>>>>>>    c - The event listener for empty queue fires to often.
>>>>>>>>    d - The default ThreadQueue is some kind of thread serializer.
>>>>>>>>    e - The DB/WMS ThreadQueue has no public access.
>>>>>>>>
>>>>>>>>    Now I've written a sub class of ThreadQueue: SingleThreadQueue
>>>>>>>>    (see attachment). This one deals with the issues a, b and d.
>>>>>>>>    I also attached a patch against RenderingManager [3] to handle e.
>>>>>>>>
>>>>>>>>    The new class (to be placed in package
>>>>>>>>    com.vividsolutions.jump.workbench.ui.renderer) is a drop-in
>>>>>>>>    replacement for the default ThreadQueue in RenderingManager.
>>>>>>>>    Not for the ThreadQueue that handles the DB/WMS layers.
>>>>>>>>
>>>>>>>>    Because Jon limited the number of parallel threads in default
>>>>>>>>    queue to 1 I see no reason why to fork a new thread for each
>>>>>>>>    Runnable it processes. Thread creation/shutdown is fairly
>>>>>>>>    expensive. Instead a single background thread is started
>>>>>>>>    which processes the Runnables one by one. If the thread
>>>>>>>>    is idle for 30 secs it shuts itself down. If you have a lot
>>>>>>>>    of (non-WMS/BB) layers this should improve performance
>>>>>>>>    and save some resources. The processing itself is done
>>>>>>>>    with a monitor (synchronized/wait/notify) so there is no
>>>>>>>>    busy wait any more.
>>>>>>>>
>>>>>>>>    The DB/WMS ThreadQueue (real parallel threads) is left untouched
>>>>>>>>    for the moment. Depending on my personal schedule I will send
>>>>>>>>    a patch against this one too. Preliminary code with thread pooling
>>>>>>>>    exists but it needs a bit more testing.
>>>>>>>>
>>>>>>>>    Find attached the new class and patches against RenderingManager and
>>>>>>>>    the old ThreadQueue to bring it to work.
>>>>>>>>
>>>>>>>>    Comments are very welcome. :-)
>>>>>>>>
>>>>>>>>    Kind regrads,
>>>>>>>>      Sascha
>>>>>>>>
>>>>>>>>    [1] com.vividsolutions.jump.workbench.ui.renderer.ThreadQueue
>>>>>>>>    [2]
>>>>>>>>    
>>>>>>>> http://sourceforge.net/mailarchive/message.php?msg_name=4653389E.6000706%40intevation.de
>>>>>>>>    [3] com.vividsolutions.jump.workbench.ui.renderer.RenderingManager
>>>>>>>>
>>>>>>>>            
>>>>>>>>
>>>>>>>> ------------------------------------------------------------------------
>>>>>>>>
>>>>>>>> /*
>>>>>>>> * The Unified Mapping Platform (JUMP) is an extensible, interactive 
>>>>>>>> GUI 
>>>>>>>> * for visualizing and manipulating spatial features with geometry and 
>>>>>>>> attributes.
>>>>>>>> *
>>>>>>>> * Copyright (C) 2003 Vivid Solutions 
>>>>>>>> * Copyright (C) 2007 Intevation GmbH
>>>>>>>> * 
>>>>>>>> * This program is free software; you can redistribute it and/or
>>>>>>>> * modify it under the terms of the GNU General Public License
>>>>>>>> * as published by the Free Software Foundation; either version 2
>>>>>>>> * of the License, or (at your option) any later version.
>>>>>>>> * 
>>>>>>>> * This program is distributed in the hope that it will be useful,
>>>>>>>> * but WITHOUT ANY WARRANTY; without even the implied warranty of
>>>>>>>> * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>>>>>>>> * GNU General Public License for more details.
>>>>>>>> * 
>>>>>>>> * You should have received a copy of the GNU General Public License
>>>>>>>> * along with this program; if not, write to the Free Software
>>>>>>>> * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  
>>>>>>>> 02111-1307, USA.
>>>>>>>> * 
>>>>>>>> * Suite #1A
>>>>>>>> * 2328 Government Street
>>>>>>>> * Victoria BC  V8T 5G5
>>>>>>>> * Canada
>>>>>>>> *
>>>>>>>> * (250)385-6040
>>>>>>>> * www.vividsolutions.com
>>>>>>>> */
>>>>>>>> package com.vividsolutions.jump.workbench.ui.renderer;
>>>>>>>>
>>>>>>>> import java.util.LinkedList;
>>>>>>>> import java.util.ArrayList;
>>>>>>>>
>>>>>>>> /**
>>>>>>>> * This thread queue executes at maximum N Runnables in parallel
>>>>>>>> * were N is a given number of worker threads that should be used.
>>>>>>>> * If N threads are running and busy each further incoming 
>>>>>>>> * Runnable is queued until one of the threads has finished its current 
>>>>>>>> job.
>>>>>>>> * If a worker thread becomes idle (no more job in the queue)
>>>>>>>> * it is hold alive for 5 seconds. If during this period of time 
>>>>>>>> * no new Runnable is enqueued the worker thread dies.
>>>>>>>> *
>>>>>>>> * @author Sascha L. Teichmann ([EMAIL PROTECTED])
>>>>>>>> */
>>>>>>>> public class ThreadQueue
>>>>>>>> {
>>>>>>>>        /** The time a worker thread stays alive if idle */
>>>>>>>>        public static final long WORKER_STAY_ALIVE_TIME = 5000L;
>>>>>>>>
>>>>>>>>        /**
>>>>>>>>         * Worker thread. Fetches Runnable from the surrounding 
>>>>>>>>         * ThreadQueue instance.
>>>>>>>>         */
>>>>>>>>        protected class Worker 
>>>>>>>>        extends         Thread
>>>>>>>>        {
>>>>>>>>                public void run() {
>>>>>>>>                        try {
>>>>>>>>                                for (;;) {
>>>>>>>>                                        Runnable runnable;
>>>>>>>>
>>>>>>>>                                        synchronized (queuedRunnables) {
>>>>>>>>                                                if 
>>>>>>>> (queuedRunnables.isEmpty()) {
>>>>>>>>                                                        
>>>>>>>> ++waitingThreads;
>>>>>>>>                                                        try {
>>>>>>>>                                                                
>>>>>>>> queuedRunnables.wait(WORKER_STAY_ALIVE_TIME);
>>>>>>>>                                                        }
>>>>>>>>                                                        catch 
>>>>>>>> (InterruptedException ie) {
>>>>>>>>                                                        }
>>>>>>>>                                                        finally {
>>>>>>>>                                                                
>>>>>>>> --waitingThreads;
>>>>>>>>                                                        }
>>>>>>>>
>>>>>>>>                                                        // if still 
>>>>>>>> empty -> die!
>>>>>>>>                                                        if 
>>>>>>>> (queuedRunnables.isEmpty())
>>>>>>>>                                                                break;
>>>>>>>>                                                }
>>>>>>>>                                                if (disposed)
>>>>>>>>                                                        break;
>>>>>>>>                                                runnable = 
>>>>>>>> (Runnable)queuedRunnables.remove();
>>>>>>>>                                        } // synchronized 
>>>>>>>> queuedRunnables
>>>>>>>>
>>>>>>>>                                        try {
>>>>>>>>                                                runnable.run();
>>>>>>>>                                        }
>>>>>>>>                                        catch (Exception e) {
>>>>>>>>                                                e.printStackTrace();
>>>>>>>>                                        }
>>>>>>>>                                } // for (;;)
>>>>>>>>                        }
>>>>>>>>                        finally { // guarantee that counter goes down
>>>>>>>>                                boolean allRunningThreadsFinished;
>>>>>>>>                                synchronized (runningThreads) {
>>>>>>>>                                        allRunningThreadsFinished = 
>>>>>>>> --runningThreads[0] == 0;
>>>>>>>>                                }
>>>>>>>>                                if (allRunningThreadsFinished)
>>>>>>>>                                        fireAllRunningThreadsFinished();
>>>>>>>>                        }
>>>>>>>>                }
>>>>>>>>        } // class Worker
>>>>>>>>
>>>>>>>>        /**
>>>>>>>>         * If the number of running threads goes down to zero
>>>>>>>>         * implementations of this interface are able to be informed.
>>>>>>>>         */
>>>>>>>>        public interface Listener {
>>>>>>>>                void allRunningThreadsFinished();
>>>>>>>>        } // interface Listener
>>>>>>>>
>>>>>>>>        /** Number of running threads */
>>>>>>>>        protected int [] runningThreads = new int[1];
>>>>>>>>
>>>>>>>>        /** max. Number of threads running parallel */
>>>>>>>>        protected int maxRunningThreads;
>>>>>>>>
>>>>>>>>        /** Number of threads that are currently idle */
>>>>>>>>        protected int waitingThreads;
>>>>>>>>
>>>>>>>>        /** The queue of Runnables jobs waiting to be run */
>>>>>>>>        protected LinkedList queuedRunnables;
>>>>>>>>
>>>>>>>>        /** Singals that the ThreadQueue is going to quit */
>>>>>>>>        protected boolean disposed;
>>>>>>>>
>>>>>>>>        /** List of Listeners */
>>>>>>>>        protected ArrayList listeners = new ArrayList();
>>>>>>>>
>>>>>>>>        /**
>>>>>>>>         * Creates a ThreadQueue with one worker thread.
>>>>>>>>         */
>>>>>>>>        public ThreadQueue() {
>>>>>>>>                this(1);
>>>>>>>>        }
>>>>>>>>
>>>>>>>>        /** Creates a ThreadQueue with a given number of worker threads.
>>>>>>>>         * @param maxRunningThreads the max. number of threads to be 
>>>>>>>> run parallel.
>>>>>>>>         */
>>>>>>>>        public ThreadQueue(int maxRunningThreads) {
>>>>>>>>                this.maxRunningThreads = Math.max(1, maxRunningThreads);
>>>>>>>>                queuedRunnables = new LinkedList();
>>>>>>>>        }
>>>>>>>>
>>>>>>>>        /**
>>>>>>>>         * Adds a Listener to this ThreadQueue.
>>>>>>>>         * @param listener the listener to add.
>>>>>>>>         */
>>>>>>>>        public synchronized void add(Listener listener) {
>>>>>>>>                if (listener != null)
>>>>>>>>                        listeners.add(listener);
>>>>>>>>        }
>>>>>>>>
>>>>>>>>        /**
>>>>>>>>         * Removes a Listener from this ThreadQueue.
>>>>>>>>         * @param listener the listener to be removed.
>>>>>>>>         */
>>>>>>>>        public synchronized void remove(Listener listener) {
>>>>>>>>                if (listener != null)
>>>>>>>>                        listeners.add(listener);
>>>>>>>>        }
>>>>>>>>
>>>>>>>>        /**
>>>>>>>>         * Informs Listeners of the fact that the number of running 
>>>>>>>> threads
>>>>>>>>         * went to zero.
>>>>>>>>         */
>>>>>>>>        protected void fireAllRunningThreadsFinished() {
>>>>>>>>                ArrayList copy;
>>>>>>>>                synchronized (this) { copy = new ArrayList(listeners); }
>>>>>>>>                for (int i = copy.size()-1; i >= 0; --i)
>>>>>>>>                        
>>>>>>>> ((Listener)copy.get(i)).allRunningThreadsFinished();
>>>>>>>>        }
>>>>>>>>
>>>>>>>>
>>>>>>>>        /**
>>>>>>>>         * The number of currently running worker threads.
>>>>>>>>         * @return number of currently running worker threads.
>>>>>>>>         */
>>>>>>>>        public int runningThreads() {
>>>>>>>>                synchronized (runningThreads) {
>>>>>>>>                        return runningThreads[0];
>>>>>>>>                }
>>>>>>>>        }
>>>>>>>>
>>>>>>>>        /**
>>>>>>>>         * The number of currently waiting Runnables.
>>>>>>>>         * @return number of currently waiting Runnables.
>>>>>>>>         */
>>>>>>>>        public int waitingRunnables() {
>>>>>>>>                synchronized (runningThreads) {
>>>>>>>>                        return queuedRunnables.size();
>>>>>>>>                }
>>>>>>>>        }
>>>>>>>>
>>>>>>>>        /**
>>>>>>>>         * The number of currently idle worker threads.
>>>>>>>>         * @return number of currently idle worker threads.
>>>>>>>>         */
>>>>>>>>        public int waitingThreads() {
>>>>>>>>                synchronized (queuedRunnables) {
>>>>>>>>                        return waitingThreads;
>>>>>>>>                }
>>>>>>>>        }
>>>>>>>>
>>>>>>>>        /**
>>>>>>>>         * Adds a Runnables to the queue. It will be run in one
>>>>>>>>         * of the worker threads.
>>>>>>>>         * @param runnable The Runnables to add
>>>>>>>>         */
>>>>>>>>        public void add(Runnable runnable) {
>>>>>>>>                int waiting;
>>>>>>>>                synchronized (queuedRunnables) {
>>>>>>>>                        if (disposed)
>>>>>>>>                                return;
>>>>>>>>                        waiting = waitingThreads;
>>>>>>>>                        queuedRunnables.add(runnable);
>>>>>>>>                        queuedRunnables.notify();
>>>>>>>>                }  // synchronized (queuedRunnables)
>>>>>>>>
>>>>>>>>                synchronized (runningThreads) {
>>>>>>>>
>>>>>>>>                        // if waitingThreads == 1 then
>>>>>>>>                        // the queuedRunnables.notify() should have 
>>>>>>>> waked it up.
>>>>>>>>
>>>>>>>>                        if (waitingThreads < 2 && runningThreads[0] < 
>>>>>>>> maxRunningThreads) {
>>>>>>>>                                ++runningThreads[0];
>>>>>>>>                                Worker w = new Worker();
>>>>>>>>                                w.setDaemon(true);
>>>>>>>>                                w.start();
>>>>>>>>                        }
>>>>>>>>                } // synchronized (runningThreads)
>>>>>>>>        }
>>>>>>>>
>>>>>>>>        /**
>>>>>>>>         * Empties the queue of waiting Runnables.
>>>>>>>>         */
>>>>>>>>        public void clear() {
>>>>>>>>                synchronized (queuedRunnables) {
>>>>>>>>                        queuedRunnables.clear();
>>>>>>>>                }
>>>>>>>>        }
>>>>>>>>
>>>>>>>>        /**
>>>>>>>>         * Shuts down the ThreadQueue.
>>>>>>>>         */
>>>>>>>>        public void dispose() {
>>>>>>>>                synchronized (queuedRunnables) {
>>>>>>>>                        disposed = true;
>>>>>>>>                        queuedRunnables.clear();
>>>>>>>>                        // wakeup idle threads
>>>>>>>>                        queuedRunnables.notifyAll();
>>>>>>>>                }
>>>>>>>>                synchronized (this) {
>>>>>>>>                        listeners.clear();
>>>>>>>>                }
>>>>>>>>        }
>>>>>>>> }
>>>>>>>> // end of file
>>>>>>>>            
>>>>>>>>
>>>>>>>> ------------------------------------------------------------------------
>>>>>>>>
>>>>>>>> -------------------------------------------------------------------------
>>>>>>>> This SF.net email is sponsored by DB2 Express
>>>>>>>> Download DB2 Express C - the FREE version of DB2 express and take
>>>>>>>> control of your XML. No limits. Just data. Click to get it now.
>>>>>>>> http://sourceforge.net/powerbar/db2/
>>>>>>>>
>>>>>>>> ------------------------------------------------------------------------
>>>>>>>>
>>>>>>>> _______________________________________________
>>>>>>>> Jump-pilot-devel mailing list
>>>>>>>> Jump-pilot-devel@lists.sourceforge.net
>>>>>>>> https://lists.sourceforge.net/lists/listinfo/jump-pilot-devel
>>>>>>>>            
>>>>>>>>
>>
>> -------------------------------------------------------------------------
>> This SF.net email is sponsored by DB2 Express
>> Download DB2 Express C - the FREE version of DB2 express and take
>> control of your XML. No limits. Just data. Click to get it now.
>> http://sourceforge.net/powerbar/db2/
>> _______________________________________________
>> Jump-pilot-devel mailing list
>> Jump-pilot-devel@lists.sourceforge.net
>> https://lists.sourceforge.net/lists/listinfo/jump-pilot-devel
>>
>>
> 
> -------------------------------------------------------------------------
> This SF.net email is sponsored by DB2 Express
> Download DB2 Express C - the FREE version of DB2 express and take
> control of your XML. No limits. Just data. Click to get it now.
> http://sourceforge.net/powerbar/db2/
> _______________________________________________
> Jump-pilot-devel mailing list
> Jump-pilot-devel@lists.sourceforge.net
> https://lists.sourceforge.net/lists/listinfo/jump-pilot-devel

-------------------------------------------------------------------------
This SF.net email is sponsored by DB2 Express
Download DB2 Express C - the FREE version of DB2 express and take
control of your XML. No limits. Just data. Click to get it now.
http://sourceforge.net/powerbar/db2/
_______________________________________________
Jump-pilot-devel mailing list
Jump-pilot-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/jump-pilot-devel

Reply via email to