Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

EclipseLink/Examples/Distributed

< EclipseLink‎ | Examples
Revision as of 09:14, 28 June 2011 by Michael.f.obrien.eclipselink.org (Talk | contribs) (DI 201: Refactor as Framework)

Contents

Distributed Enterprise Case Study using JEE6 API - JPA 2.0, JSF 2.0, and EJB 3.1

  • This case study describes the analysis, design, implementation and deployment of a distributed Java EE 6 application that makes use of the EJB 3.1, JPA 2.0, JSF 2.0, Servlet 3.0 and JAX-RS API implemented as part of the Oracle GlassFish Server 3.1 distribution. The Java IDE used for this tutorial is the tightly integrated SUN NetBeans 7.0 release that will be in beta until April 2011 but we are also able to use Eclipse Helios 3.6 as well.
  • This tutorial also demostrates use of alternate client and presentation frameworks that include any number of concurrent distributed Java SE clients that get, process and submit work units to the central server via a remote stateless session bean instance.
  • Why distributed? We need to investigate the concurrent behavior and exception handling of a near-real-world hammering of a server based JPA application from multiple clients. We need an architecture that induces contention for shared memory (as either static variables or shared database records). Specifically I am interested in how we implement 2-phase commit, handle OptimisticLockExceptions and design for a mix of transaction types involving REQUIRES(default)|REQUIRES_NEW|NOT_REQUIRED - which happen to be the only types supported by EJB 3.1 @Asynchronous beans. We may also integrate different isolation levels.
  • We will be concentrating on how to leverage the features of JPA 2.0 that are implemented by EclipseLink 2.x. These features should include...

Document History

Date Author Version Description & Notes
20110209 Michael O'Brien 1.0 Initial draft starting

Source

Warning2.png
Work in progress
This implementation has not been fully completed yet - however it is fully functional using manual deployment of the remote clients.


Technology Summary

  • Technology Statement: Develop a n:1 distributed application with many clients connected to one central persistence server
  • Normally we do not decide on what APIs will be in use before we analyse the requirements. However, here is the list of technologies we are using - as we finalize the implementation.
    • JPA 2.0 : All database interaction will be on the main server via a container managed @PersistenceContext on EJB session beans. The clients will modify detached entities and return them to the server for merging/persistence.
    • JTA : We will continue to use container managed transactions via the dependency injected proxy so we do not have to manage transaction events ourselves
    • EJB 3.1 : We will be using @Stateless or @Stateful (depending on our level of conversational state) @Remote beans but may be using @Singleton and/or container-managed JTA persistence units in the WAR for @Local beans
      • We will likely require the use of @Asynchronous methods or beans to enable greater parallism
      • Part of our strategy of handling OptimisticLockExceptions may involve @Singleton beans.
    • JSF 2.0 : We will use the existing @ManagedBean and new .XHTML controller/view separation pattern
    • JAX-RS 1.1 : The ability to to get/put/delete/update operations on URL resources will be required
    • JMS : An external message consumer will be used to do asynchronous operations such as collating data
    • JNI :(possible optimization via C++ using either IA32/64, SSE or even CUDA) - where the Java client is just the wrapper around the computation engine.
  • We will not be using an L2 cache such as Coherence, ehCache or Terracotta at this point - we will be communicating using standard EJB beans such as session or message-driven beans.
  • We will be presenting 2 identical implementations of this project
    • One in NetBeans projects will deploy to GlassFish
    • The other in Eclipse projects will deploy to WebLogic
  • Here is a screen capture of the current state of our UI development for this Java EE application. On the left we have a brute force live AJAX client connected to a standard Servlet, on the right we have a Java EE 6 JSF 2.0 .xhtml client. Both are backed by a @ManagedBean injected with a @EJB session bean that is injected with a @PersistenceContext.

Collatz ajax and JSF2 xhtml client cap.JPG

Problem

  • Instead of the usual Employee demo or even the simple entity/jsp format of previous JPA tutorials - we will attempt at providing a usefull distributed java application that could be deployed to a live server that would be hosted outside our firewall.
  • We require a real-world distributed app that can be used as a case study for the following issues.
    1. - performance (we need a way to hammer a JPA based server and change the client load at runtime)
    2. - management (test framework to try out central management of the server and clients)
    3. - analytics (how can we report JVM and persistence metrics with minimal thread overhead)
    4. - concurrency (how do we handle under-load forced OptimisticLockException conditions without resorting to synchronized blocks) - @Singleton SB for example
    5. - distributed memory (for clients that are not running persistence on their own - where they would benefit from an L2 cache like Coherence, ehCache or Terracotta - we need to prototype scenarios where the database or EclipseLink L1 in-memory cache can act as a distributed shared memory for the remote clients).
    • Specifically, how do we propagate changes from some clients to others using both a
      • 1) Star network of ManyToOne for clientsToServer
      • 2) Mesh network of ManyToMany for clientsToClients
    • We will develop a n:1 distributed application with many clients connected to one central persistence server.
  • 20110226: After working on this for a couple weeks I realized that I was re-inventing MapReduce - originally developed by Google - where a work unit is mapped to a distributed network of processors (possibly recursively) and then reduced back into a single solution by merging the results of the mapped sub-problems. However, this distributed system is more complicated and specializes in continuous packet distribution and collation.
  • Our selected real-world problem is a type of Blue-Sky algorithm. However, in reality it can be regarded as a kind of toy problem or other easily parallizable problem like the Mandelbrot set. For example, in the performance section below we illustrate the proof that distributing the calculations as evenly as possible over all the cores in an individual node - generates significant almost O(n) performance gains. In the graph below of several performance runs on an Intel Core i7-920 we decreased an 800 second zoom calculation to 67 seconds by using up to 512 threads for a problem size of 1024 lines.

Corei7 920 zoom time 1 to 512 threads graph.JPG

  • How can we help prove the Collatz conjecture (or all integer paths lead to 1).
  • The collatz problem presents us with several attributes that are very helpful in solving concurrency issues.
    • 1) SIMD: Each calculation of an individual collatz sequence is independent of any other - it can be done in parallel - however the threads are not synchronized and are therefore a type of MIMD processing.
    • 2) Asynchronous threads: different calculation times for different data sets requires a thread scheduler.
    • 3) Shared Memory: Optimization requires data sharing between threads

Collatz Numbers

  • Actually, since Collatz cannot be solved - it is a "research problem" - Richard Bellman and Donald K. Knuth.
  • In the interest of advancing science - specifically the science of very large (and I mean very large) as in near googol class numbers and their sequences.
  • The Collatz conjecture or (3n + 1) problem has not been proven yet. There have been attempts at verifying collatz up to 2^61 - however, massive amounts of scalar processing power is required to do this because the problem is non-linear and therefore must be brute force simulated even with optimizations.
  • The algorithm is as follows for the set of positive integers to infinity.
    • odd numbers are transformed by 3n + 1
    • even numbers are divided by 2
    • all numbers eventually reach the sequence 4-2-1
    • The Collatz Conjecture stetes that all sequences end in 1 - we just cannot prove this yet without brute force simulation - this is the goal of this search and this distributed application.
  • If you think in base 2, we see that for odd numbers we shift bits to the left, add the number to the result and set bit 0. For even numbers we shift bits to the right. We therefore have a simplified algorithm as follows.
odd: next binary = number << 1 + number + 1 
even: next binary = number >> 1 

or the following combined odd + even rule where we do both steps at once

odd: next binary = number >> 1 + number + 1

- this result I found is sort of odd and surprising as it differs only in the direction of the shift.

  • Here is an example of the sequence for number 27.
  • This number reaches a maximum of 9232 during a path of 110 before it reaches the terminating sequence 4-2-1.

Collatz n27 p110 m9232.JPG

  • Here is the graph of the sequence for 670,617,279 with a path of 986 and a maximum of 966,616,035,460

Collatz n670617279 p986 m966616035460.JPG

  • Observation 1: the maximum value remains at or around 2x the number of bits in the start number - at least so far in my own simulations up to 640 billion.
  • We stop iteration and record the max path and max value when the sequence enters the 4-2-1 loop after the first 1 is reached. This sequence must be simulated for all positive integers up to the limit of the software being used. Fortunately, in Java (and .NET3) we can use BigInteger which supports unlimited length integers - as we would quickly overflow using a 64 bit long as soon as we started iterating numbers over 32 bits.

Requirements

R0: Unbounded Scalar Precision

  • Actually essentially unbounded integer precision is required - but if we (my research (the R in R&D) division at Oracle anyway) are going to persist something we need a set column size. I think we are safe with 256 or 512 bit precision for now.
  • Java (and lately .NET and android). We could use the more efficient BitSet for binary operations but it won't help us because the bit length is fixed at 64 bits - we need at least 256. We will also need a conversion strategy for persisting unlimited numbers into limited length NUMERIC database fields.
  • The Long datatype in Java and the corresponding __int64 datatype in C/C++ (Visual Studio 10) and the BIGINT datatype in SQL - all overflow at 64 bits which can address an Exabyte or represent the unsigned scalar 10^18 which is 18,446744,073709,551616 or 18 Quintillion.

R1: Increased Superscalar Performance

  • We need to get better performance from a group of separate JVM's running in parallel on the same or different machines that we would get from a single instance of the client.
  • The impact of distribution and processing of the client data packets should incur very little overhead on the central processing server.
  • However the possiblity for shared memory contention (the current maximums) will be fierce - and will require a strategy for handling OptimisticLockExceptions when attempting to update the same record in the database (where the version field will be different as a result of out of order execution).

R1: Local Client Access

  • JSF browser based console will be developed

R2: Remote RMI Client Access Inside Firewall

  • EJB 3.x remote session beans will be available

R3: Remote WebService Client Access Outside Firewall

  • We will implement this by generating a WSDL from the JPA model and exposing a web service facade around the EJB 3.x session beans

R4: Browser based Interface to Client Data on Server

  • We will implement this using JSF 2.0 to start.

R5: Separation of components and concerns

  • The data model in the form of a JPA persistence context will be in a separate JAR project allowing us to share the model among the EJB beans, the WAR web project and the distributed clients of the business layer that includes the SE clients, the web services clients and any JMS client listeners.

R6: Full abstraction of the database

  • We will use JPA 2.0 to manage the persistence of the model.

R7: JEE6 API usage

  • Where available we will leverage any JEE6 features that help our implentation

R: Remote Update

  • We need some sort of utility that will remote update all the client code (includes client classes and EJB session bean interfaces).
  • We will likely use Java Web Start to initially download the SE client and to keep it current.

http://download.oracle.com/javase/6/docs/technotes/guides/javaws/developersguide/launch.html#creating

R: Thread Modulation

  • A way to reduce the processor load of individual clients would be very usefull in allowing the overall distributed system to be throttled down (likely with wait states). We would perform a process very similar to PWM (pulse width modulation) - used for example in brightness control of LED systems by varying the on time square wave of a signal. In our case will could increase the thread wait/suspend time from 0=default to something like 60 sec.


Analysis

  • Like all architectural projects - we will proceed in 3 phases.
    1. Develop the API
    2. Optimize Performance
    3. Optimize Volumetrics
  • This collatz application is an example of an Embarassingly Parallel Problem.
  • The solution or simulation of this problem is easily described by a SIMD (Single Instruction Multiple Data) architecture - where each thread can run independently using the same algorithm on its own data set. There is however a part of this problem that requires synchronization between the threads - the determination of the global maximums.

Data Model

  • The following UML class diagram details the data model for the business objects. We will be using JPA entities and mappedsuperclass artifacts.
  • Initially I used aggregation and a unidirectional @OneToOne from a Maximum or Path entity to a CollatzRecord entity to differentiate value maximums from path maximums (IE: for start #27, the value maximum is 9232 and the path maxiumum is 110 iterations).

Collatz uml class.jpg

  • After some simulation it became apparent that the schema needs an inheritance model where PathRecord and MaximumRecord should subclass from CollatzRecord instead.

Entity - UnitOfWork

@Entity
@TypeConverter(name="BigIntegerToString",dataType=String.class,objectType=BigInteger.class)
public class UnitOfWork implements Serializable {
    private static final long serialVersionUID = 3287861579472326552L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @OneToMany(mappedBy="unitOfWork", fetch=FetchType.EAGER,cascade=CascadeType.ALL)
    // record are an ordered list
    private List<CollatzRecord> records;
 
    @OneToOne(cascade=CascadeType.ALL)
    private ActiveProcessor processor;
    @Column(nullable=false, length=512)
    @Convert("BigIntegerToString")
    private BigInteger initial;
    @Column(nullable=false, length=512)
    @Convert("BigIntegerToString")
    private BigInteger extent;
    @OneToOne
    private CollatzRecord knownMax;
    @OneToOne
    private CollatzRecord knownPath;
    private Long startTimestamp;
    private Long endTimestamp;
    @Column(nullable=false) // nullable to avoid /0 error
    private long operations;
 
    private Integer retries;
 
    // Note: These two may be the same record
    @OneToOne // unidirectional OneToOne
    private CollatzRecord maxPathRecord;
    @OneToOne // unidirectional OneToOne    
    private CollatzRecord maxValueRecord;
 
    @Version
    private Long version;
 
    // Cached values - do not persist
    @Transient
    private BigInteger interval;
 
    public static final BigInteger TWO = BigInteger.valueOf(2);
    public static final BigInteger FOUR = BigInteger.valueOf(4);
    public static final long R2000 = 67457283406188652L;
 
    // business methods (separate from DTO methods)
    public long processInterval() {
        StringBuffer buffer = null;
        long operations = 0L;        
        BigInteger pathIteration = BigInteger.ONE;//, path should fit in 64 bits
        BigInteger maxValueIteration = BigInteger.ONE;
        boolean milestone = false;
        String prefix = null;
        //System.out.println("_collatz: " + System.currentTimeMillis() + ": threads,Interval,start,end:    " + processor.getThreads() + "," + getInterval() + "," + initial + "," + extent);
        List<BigInteger> list = new ArrayList<BigInteger>();        
        BigInteger currentNumber = initial;
        CollatzRecord record = null;
        setStartTimestamp(System.currentTimeMillis());
        while (currentNumber.compareTo(extent) < 0) {
        	list = hailstoneSequenceUsingBigInteger(list, currentNumber);
            // cache maxPath and maxValue for performance : were doing 10000 times the iterations required
            if(!list.isEmpty()) {
                maxValueIteration = list.remove(0);
                pathIteration = list.remove(0);
                operations = operations + pathIteration.longValue();
                // keep track of local milestones
                if(pathIteration.compareTo(getMaxPath()) > 0) {                    
                    milestone = true;
                    prefix = "P";
                    record = new CollatzRecord();
                    record.setPathLength(pathIteration);
                    record.setMaximum(maxValueIteration);
                    //setMaxPath(getMaxPath());
                    setMaxPathRecord(record);
                    record.setIsPathRecord(true);
                    // update cache
                    //maxPathLocal = pathIteration;
                }
                if(maxValueIteration.compareTo(getMaxValue()) > 0) {                    
                    if(milestone) {
                        prefix = "PM";
                    } else {
                        prefix = "M";
                        record = new CollatzRecord();
                        record.setPathLength(pathIteration);
                        record.setMaximum(maxValueIteration);
                        milestone = true;
                    }
                    setMaxValueRecord(record);
                    record.setIsMaxRecord(true);
                }
                if(milestone) {
                    record.setUnitOfWork(this);
                    record.setInitial(currentNumber);
                    addRecord(record);
                    buffer = new StringBuffer("_collatz: ");
                    buffer.append(System.currentTimeMillis());
                    buffer.append(": ");
                    buffer.append(getProcessor().getIdentifier());
                    buffer.append(": ");
                    buffer.append(prefix);
                    buffer.append(",");
                    buffer.append(this.getInterval());
                    buffer.append(",");
                    buffer.append(currentNumber);
                    buffer.append(",");
                    buffer.append(getMaxPath());// + PATH_OFFSET_FOUR); // we stop the search at 4 (so add a path of 2)
                    buffer.append("\t");
                    buffer.append(",");
                    buffer.append(maxValueIteration); // BigInteger implements Comparable
                    buffer.append("\t");
                    if((maxValueIteration.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0)) {
                        buffer.append("2^63+,");
                        buffer.append(maxValueIteration.subtract(BigInteger.valueOf(Long.MAX_VALUE)));
                    } else {
                        buffer.append(",");
                    }
                    System.out.println(buffer.toString());
                    milestone = false;
                }
            }
            // increment
            currentNumber = currentNumber.add(TWO);
        }
        setEndTimestamp(System.currentTimeMillis());
        setOperations(operations);
        return operations;   
    }
 
    /**
     * Return the range between the start and end points for this UnitOfWork packet
     * @return
     */
    public BigInteger getInterval() {
        if(null == interval) {
            // lazy load the interval
            interval = extent.subtract(initial);
        }
        return interval;
    }
 
    /**
     * Compute the hailstone (collatz) sequence for the start BigInteger.
     * Return a List of the maximum value and maximum path in this iteration.
     * @param list
     * @param start
     * @return
     */
    public List<BigInteger> hailstoneSequenceUsingBigInteger(List<BigInteger> list, BigInteger start)  {
    	BigInteger max = start;;
    	long path = 1;// - 0;//PATH_OFFSET_FOUR;
    	if(start.equals(BigInteger.ZERO) || start.equals(BigInteger.ONE)) {
    	    list.add(max);
    	    list.add(BigInteger.valueOf(path));
    	    return list;
    	}
    	BigInteger current = start;
    	while (current.compareTo(FOUR) > 0) { // Perf
    	    if(current.testBit(0)) { // test odd
    	        current = current.shiftLeft(1).setBit(0).add(current);
    	    } else {
    	        current = current.shiftRight(1);
    	    }
    	    // check max
    	    if(max.compareTo(current) < 0) {
    	        max = current;
    	    }
    	    path += 1;
    	}
    	list.add(max);
    	list.add(BigInteger.valueOf(path));
    	return list;
    }
 
    // Note: this function will overflow at 64 bits
    public List<BigInteger> hailstoneSequenceUsingLong(List<BigInteger> list, BigInteger start)  {
        BigInteger max = start;;
        long path = 1;// - 0;//PATH_OFFSET_FOUR;
        if(start.equals(BigInteger.ZERO) || start.equals(BigInteger.ONE)) {
            list.add(max);
            list.add(BigInteger.valueOf(path));
            return list;
        }
        BigInteger current = start;
        while (current.compareTo(FOUR) > 0) { // Perf
            if(current.testBit(0)) { // test odd
                current = current.shiftLeft(1).setBit(0).add(current);
            } else {
                current = current.shiftRight(1);
            }
            // check max
            if(max.compareTo(current) < 0) {
                max = current;
            }
            path += 1;
        }
        list.add(max);
        list.add(BigInteger.valueOf(path));
        return list;
    }
 
    public long getOperationsPerSecond() {
        long operationsPerSecond;
        long duration = getEndTimestamp() - getStartTimestamp();
        if(duration > 0) {
            operationsPerSecond = (1000 * operations) / duration;
        } else {
            operationsPerSecond = 0;
        }
        return operationsPerSecond;
    }
 
    // Not really MIPS - but millions of iterations per second
    public long getMIPS() {
        return getOperationsPerSecond() / 1000000;
    }
 
    @Override
    public int hashCode() {
        int hash = 0;
        hash += (id != null ? id.hashCode() : 0);
        return hash;
    }
 
    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof UnitOfWork)) {
            return false;
        }
        UnitOfWork other = (UnitOfWork) object;
        if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
            return false;
        }
        return true;
    }
 
    @Override
    public String toString() {
        StringBuffer aBuffer = new StringBuffer("UnitOfWork");//getClass().getSimpleName());
        aBuffer.append("@");
        aBuffer.append(hashCode());
        aBuffer.append("( id: ");
        aBuffer.append(getId());
        aBuffer.append(" s:");
        aBuffer.append(initial);
        aBuffer.append(")");
        return aBuffer.toString();
    }    
 
    public void addRecord(CollatzRecord record) {
        if(null == records) {
            records = new ArrayList<CollatzRecord>();
        }
        records.add(record);        
    }
 
    // Composite get/set
    public BigInteger getMaxPath() {        return getMaxPathRecord().getPathLength();    }
    public BigInteger getMaxValue() {        return getMaxValueRecord().getMaximum();    }
 
    // Simple get/set
    public Long getId() {        return id;    }
    public void setId(Long id) {        this.id = id;    }
    public Processor getProcessor() {        return processor;    }
    public void setProcessor(ActiveProcessor processor) {        this.processor = processor;    }
    public BigInteger getInitial() {        return initial;    }
    public void setInitial(BigInteger initial) {        this.initial = initial;    }
    public BigInteger getExtent() {        return extent;    }
    public void setExtent(BigInteger extent) {        this.extent = extent;    }
    public CollatzRecord getKnownMax() {        return knownMax;    }
    public void setKnownMax(CollatzRecord knownMax) {        this.knownMax = knownMax;    }
    public CollatzRecord getKnownPath() {        return knownPath;    }
    public void setKnownPath(CollatzRecord knownPath) {        this.knownPath = knownPath;    }
    public Long getStartTimestamp() {        return startTimestamp;    }
    public void setStartTimestamp(Long startTimestamp) {        this.startTimestamp = startTimestamp;    }
    public Long getEndTimestamp() {        return endTimestamp;    }
    public void setEndTimestamp(Long endTimestamp) {        this.endTimestamp = endTimestamp;    }
    public Integer getRetries() {        return retries;    }
    public void setRetries(Integer retries) {        this.retries = retries;    }
    public Long getVersion() {        return version;    }
    public void setVersion(Long version) {        this.version = version;    }
    public List<CollatzRecord> getRecords() {        return records;    }
    public void setRecords(List<CollatzRecord> records) {        this.records = records;    }
    public long getOperations() {        return operations;    }
    public void setOperations(long operations) {        this.operations = operations;    }
    public CollatzRecord getMaxPathRecord() {        return maxPathRecord;    }
    public void setMaxPathRecord(CollatzRecord maxPathRecord) {        this.maxPathRecord = maxPathRecord;    }
    public CollatzRecord getMaxValueRecord() {        return maxValueRecord;    }
    public void setMaxValueRecord(CollatzRecord maxValueRecord) {        this.maxValueRecord = maxValueRecord;    }    
}

MappedSuperclass - Processor

@MappedSuperclass
public abstract class Processor implements Serializable {
    private static final long serialVersionUID = 7629253037384373990L;
    private Integer rank;
    private Integer performance;
    private Integer cores;
    private Integer threads;
    @Column(name="IDENT")
    private String identifier;
 
    public Integer getRank() {        return rank;    }
    public void setRank(Integer rank) {        this.rank = rank;    }
    public Integer getPerformance() {        return performance;    }
    public void setPerformance(Integer performance) {        this.performance = performance;    }
    public Integer getCores() {        return cores;    }
    public void setCores(Integer cores) {        this.cores = cores;    }
    public Integer getThreads() {        return threads;    }
    public void setThreads(Integer threads) {        this.threads = threads;    }
    public String getIdentifier() {        return identifier;    }
    public void setIdentifier(String identifier) {        this.identifier = identifier;    }
}

MappedSuperclass - DistributedProcessor

  • Empty implementation for now.
@MappedSuperclass
public abstract class DistributedProcessor extends Processor {
    private static final long serialVersionUID = -8570686397938308254L;
    // Caused by: org.hibernate.MappingException: Could not determine type for: java.util.Collection, for columns: [org.hibernate.mapping.Column(ip)]
    // TODO: use JPA2 ElementCollection
}

Entity ActiveProcessor

@Entity
public class ActiveProcessor extends DistributedProcessor implements Serializable {
    private static final long serialVersionUID = 6472979075266547411L;
 
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
 
    @OneToOne
    private UnitOfWork activeUnitOfWork;
 
    private int category;
 
    @Version
    private Long version;
 
    @Override
    public int hashCode() {
        int hash = 0;
        hash += (id != null ? id.hashCode() : 0);
        return hash;
    }
 
    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof ActiveProcessor)) {
            return false;
        }
        ActiveProcessor other = (ActiveProcessor) object;
        if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
            return false;
        }
        return true;
    }
 
    @Override
    public String toString() {
        StringBuffer aBuffer = new StringBuffer(getClass().getSimpleName());
        aBuffer.append("@");
        aBuffer.append(hashCode());
        aBuffer.append("( id: ");
        aBuffer.append(getId());
        aBuffer.append(")");
        return aBuffer.toString();
    }    
 
    public UnitOfWork getActiveUnitOfWork() {        return activeUnitOfWork;    }
    public void setActiveUnitOfWork(UnitOfWork activeUnitOfWork) {        this.activeUnitOfWork = activeUnitOfWork;    }
    public int getCategory() {        return category;    }
    public void setCategory(int category) {        this.category = category;    }
    public Long getVersion() {        return version;    }
    public void setVersion(Long version) {        this.version = version;    }
    public Long getId() {        return id;    }
    public void setId(Long id) {        this.id = id;    }
}

Entity - CollatzRecord

@Entity
@TypeConverter(name="BigIntegerToString",dataType=String.class,objectType=BigInteger.class)
public class CollatzRecord implements Serializable {
    private static final long serialVersionUID = 4023830926240714638L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @ManyToOne(fetch=FetchType.EAGER)
    private UnitOfWork unitOfWork;
    @Column(nullable=false, length=512)
    @Convert("BigIntegerToString")
    private BigInteger initial;
    @Column(nullable=false, length=512)
    @Convert("BigIntegerToString")
    private BigInteger pathLength;
    @Column(nullable=false, length=512)
    @Convert("BigIntegerToString")
    private BigInteger maximum;
    /** A value of true means this is a maximum value record (independent of max path) */
    @Basic
    private boolean isMaxRecord;
    /** A value of true means this is a maximum path record (independent of max value) */
    @Basic
    private boolean isPathRecord;
 
    @Version
    private Long version;
 
    @Override
    public int hashCode() {
        int hash = 0;
        hash += (id != null ? id.hashCode() : 0);
        return hash;
    }
 
    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof CollatzRecord)) {
            return false;
        }
        CollatzRecord other = (CollatzRecord) object;
        if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
            return false;
        }
        return true;
    }
 
    @Override
    public String toString() {
        StringBuffer aBuffer = new StringBuffer(getClass().getSimpleName());
        aBuffer.append("@");
        aBuffer.append(hashCode());
        aBuffer.append("( id: ");
        aBuffer.append(getId());
        aBuffer.append(")");
        return aBuffer.toString();
    }    
 
    public Long getId() {        return id;   }
    public void setId(Long id) {        this.id = id;    }
    public BigInteger getInitial() {        return initial;    }
    public void setInitial(BigInteger initial) {        this.initial = initial;    }
    public BigInteger getPathLength() {        return pathLength;    }
    public void setPathLength(BigInteger pathLength) {        this.pathLength = pathLength;    }
    public BigInteger getMaximum() {        return maximum;    }
    public void setMaximum(BigInteger maximum) {        this.maximum = maximum;    }
    public Long getVersion() {        return version;    }
    public void setVersion(Long version) {        this.version = version;    }
    public UnitOfWork getUnitOfWork() {        return unitOfWork;    }
    public void setUnitOfWork(UnitOfWork unitOfWork) {        this.unitOfWork = unitOfWork;    }
    public boolean isMaxRecord() {    	return isMaxRecord;    }    
    public boolean isPathRecord() {    	return isPathRecord;    }
    public void setIsMaxRecord(boolean max) {    	isMaxRecord = max;    }
    public void setIsPathRecord(boolean path) {    	isPathRecord = path;    }
}

Entity - ComputeGrid

@Entity
@TypeConverters({@TypeConverter(name="BigIntegerToString",dataType=String.class,objectType=BigInteger.class)})
public class ComputeGrid implements Serializable {
    private static final long serialVersionUID = 5273909837574142903L;
 
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
 
    // Note: These two may be the same record
    @OneToOne // unidirectional OneToOne
    private CollatzRecord maxPathRecord;
    @OneToOne // unidirectional OneToOne    
    private CollatzRecord maxValueRecord;
 
    @Column(length=512)
    @Convert("BigIntegerToString")
    private BigInteger nextNumberToSearch;
    private Long globalStartTimestamp;
    @Column(name="globalduration", length=512)
    @Convert("BigIntegerToString")
    private BigInteger globalDuration;
    @Column(name="operations",nullable=false,length=512) // nullable to avoid /0 error
    //@Column(nullable=false,length=512) // nullable to avoid /0 error
    @Convert("BigIntegerToString")
    private BigInteger operations;
 
    @Column(name="bestIterationsPerSecond", length=512)
    @Convert("BigIntegerToString")
    private BigInteger bestIterationsPerSecond;
    @Column(name="partitionLength", length=512)
    @Convert("BigIntegerToString")
    private BigInteger partitionLength;
 
    @Version
    private Long version;
 
    @Override
    public int hashCode() {
        int hash = 0;
        hash += (id != null ? id.hashCode() : 0);
        return hash;
    }
 
    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof ComputeGrid)) {
            return false;
        }
        ComputeGrid other = (ComputeGrid) object;
        if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
            return false;
        }
        return true;
    }
 
    @Override
    public String toString() {
        StringBuffer aBuffer = new StringBuffer(getClass().getSimpleName());
        aBuffer.append("@");
        aBuffer.append(hashCode());
        aBuffer.append("( id: ");
        aBuffer.append(getId());
        aBuffer.append(")");
        return aBuffer.toString();
    }    
 
    // Composite get/set
    public BigInteger getMaxPath() {        return getMaxPathRecord().getPathLength();    }
    public BigInteger getMaxValue() {        return getMaxValueRecord().getMaximum();    }
 
    // Simple get/set
    public CollatzRecord getMaxPathRecord() {        return maxPathRecord;    }
    public void setMaxPathRecord(CollatzRecord maxPathRecord) {        this.maxPathRecord = maxPathRecord;    }
    public CollatzRecord getMaxValueRecord() {        return maxValueRecord;    }
    public void setMaxValueRecord(CollatzRecord maxValueRecord) {        this.maxValueRecord = maxValueRecord;    }    
    public Long getId() {        return id;    }
    public void setId(Long id) {        this.id = id;    }
    public BigInteger getNextNumberToSearch() {        return nextNumberToSearch;    }
    public void setNextNumberToSearch(BigInteger nextNumberToSearch) {        this.nextNumberToSearch = nextNumberToSearch;    }
    public Long getGlobalStartTimestamp() {        return globalStartTimestamp;    }
    public void setGlobalStartTimestamp(Long globalStartTimestamp) {        this.globalStartTimestamp = globalStartTimestamp;    }
    public BigInteger getGlobalDuration() {        return globalDuration;    }
    public void setGlobalDuration(BigInteger globalDuration) {        this.globalDuration = globalDuration;    }
    public BigInteger getBestIterationsPerSecond() {        return bestIterationsPerSecond;    }
    public void setBestIterationsPerSecond(BigInteger bestIterationsPerSecond) {        this.bestIterationsPerSecond = bestIterationsPerSecond;    }
    public Long getVersion() {        return version;    }
    public void setVersion(Long version) {        this.version = version;    }
    public BigInteger getPartitionLength() {        return partitionLength;    }
    public void setPartitionLength(BigInteger partitionLength) {        this.partitionLength = partitionLength;    }
    public BigInteger getOperations() {		return operations;	}
	public void setOperations(BigInteger operations) {		this.operations = operations;	}
}

Shared Memory

AI1: Unidirectional or Bidirectional communication between clients and server

  • At this point we will be implementing a protocol similar to stateless HTTP where each client requests from or posts resources to the server. The server does not initiate communication - it only responds to clients.
  • Alternatively we will likely add JMS listener registration where the server will post messages to clients and the clients that subscribe to the JMS queue may choose to asynchronously respond to the message.

AI2: Synchronous or Asynchronous access to session beans from clients

  • We have the choice of getting a reference to a remote session bean and holding that reference for the duration of the client work packet until we return results to the server. Or, we can perform separate calls to separate references to get and put the work unit. It will depend on the length of time to process the unit, the bean lifetime and how many beans are in the server pool.

AI3: JEE6 Technology State for major EE Servers

  • As we will be deploying at least one implementation to one of the major EE servers - we need some selection criteria.

AI4: Multiple Java Processes or Multiple Threads for Multi-Core Computers

  • Using all the physical cores on a multicore processor significantly increases performance. For example I get around a 350% speedup if I use all four physical cores of an Intel Corei7 processor. Using the other 4 hyperthreaded cores starts to slow down all the cores significantly though.
  • In order to use the cores of a system we can either run multiple instances of our client code or we can spawn multiple threads from a single application - as long as we use a 1:1 ration of threads to physical cores.
  • We need to answer the question - should I use the hyperthreaded cores as well?

WebLogic 10.3.4.0

  • Oracle WebLogic 10.3.4.0 was released on 15 Jan 2011, the following list of JEE6 APIs are implemented on top of its JEE5 certification.

WebLogic JEE6 Functionality

  • Java Persistence 2.0 (JSR 317) - with patch
  • CDI (Contexts and Dependency Injection) (JSR 299)
  • DI (Dependency Injection) for Java (JSR 330)
  • JAX-RS (RESTful Web Services) 1.1 (JSR 311)
  • JSF (Java Server Faces) 2.0 (JSR 314)

WebSphere 8.0 Beta

https://www14.software.ibm.com/iwm/web/cc/earlyprograms/websphere/wsasoa/index.shtml

WebSphere JEE6 Functionality

  • Java Persistence 2.0 (JSR 317)
  • CDI (Contexts and Dependency Injection) (JSR 299)
  • JSP 2.2 / Servlet 3.0 which includes @WebServlet
  • JAX-RS (RESTful Web Services) 1.1 (JSR 311)
  • partial JAX-WS 2.2
  • JSF (Java Server Faces) 2.0 (JSR 314)
  • EJB (Enterprise Java Beans) 3.1 (JSR 318)
  • JCA 1.6

GlassFish 3.1

  • Oracle GlassFish Server 3.1 is due for release this year. The IDE of choice is the tightly integrated Netbeans 7.0 Beta or

6.9.1

GlassFish JEE6 Functionality

  • Java Persistence 2.0 (JSR 317) - with patch
  • CDI (Contexts and Dependency Injection) (JSR 299)
  • DI (Dependency Injection) for Java (JSR 330)
  • JSP 2.2 / Servlet 3.0 which includes @WebServlet
  • Bean Validation (JSR 303)
  • JAX-RS (RESTful Web Services) 1.1 (JSR 311)
  • JAX-WS 2.2
  • JSF (Java Server Faces) 2.0 (JSR 314)
  • EJB (Enterprise Java Beans) 3.1 (JSR 318)
  • Web Profile
    • Including EJB Lite
  • JCA 1.6

JBoss 6

JBoss JEE6 Functionality

AI4: Network Topology

Use Cases

UC1: Request unit of work

UC2: Post completed unit of work

  • This use case is where most of our concurrency issues will arise. If the period of the work unit is small enough and we start getting results returned to the server at less than one per second we will see a lot of OptimisticLockExceptions when accessing shared memory (or records) because the value may have been modified in the short time between a read/update by another thread. We see this in production if the period is less than 15 bits.
  • The solution to this will likely be any of EJB 3.1 @Asynchronous methods or beans, use of sychronized blocks, use of @Stateful beans or some sort of retry mechanism.

Variant Use Cases

UC101: Communication Errors

UC101.1: RMI Host not available

UC101.1: RMI Bean not available

UC101.1: RMI Host busy

UC102: Handle discarded unit of work

Algorithm Optimization

  • Brute force simulation does not work when trying to prove Collatz. We need to with overly optimistic enthusiasm apply what we know about the behavior of hailstone numbers.
  1. Even numbers don't reach milestones - especially powers of 2 which reach 1 in the fastest time possible log(2)n (kind of the opposite of milestones)
  2. path sequences repeat - we can lookup parts of the current path/orbit based on already completed sequences in the solution tree

O1: Optimization by Truncation

  • Assumption: This optimization depends on whether we need to actually compute the paths and maximums for a range of values 'below a higher range that jas just found new maximum value and maximum path attributes - rendering our current lower search kind of irrelevant. Except in the case where the sub-path is required for other types of optimization.
  • I have determined - via a week of simulation distributed among 16 different machines in parallel - that we will need native computation.
  • Lets put things in perspective:
    • With brute force Java on around 8 parallel JVM's I can search around 1 million (~2^20) number sequences per second. At this rate, in order to search past the current record at 64 bits I would need 2 ^ (64-20) seconds = 2^44 seconds. Since there are about 31.5 million seconds in a year - or roughtly (2^25) - I would still need 2 ^ (44-25) = 2^19 years. This works out to just over half a million years.
    • Obviously I need to increase the efficiency of my search and/or incorporate x86/SSE/GPU native scalar C/C++ optimized DLLs and link to them via JNI. I require an increase of at least 6 orders of magnitude - likely 3 orders of magnitude will need to be in minimizing the search path by keeping track of past paths keyed by start number in a HashMap.
    • Contrary to traditional computer science doctrine - every software developer benefits from being architecture-aware. Knowledge of the underlying hardware, operating system and implementation language is required. For example...
      • 1) If you are running directly on a multi-core (quad-core) machine as opposed to a virtual machine (cloud) image - you will be able to take advantage of the parallelism available in the former (direct-OS) but not the latter (cloud) without replicating the cloud instances.
      • 2) When we develop in Java - a knowledge of the fact that we are actually running compiled C/C++ machine code will aide us in optimizing for word boundaries. An example is the speed up of any use of Long (64-bit words) on 64-bit native operating systems like Windows 7

Design

DI 1: Distributed Communication Strategy

  • How are we going to link the distributed clients? Are we linking the one-to-many or many-to-many where all clients communicate with each other (which would necessitate multiple EE servers).
  • 1) Multiple SE clients linked to multiple SE clients - non EE
  • 2) Multiple EE clients linked to multiple SE clients - overhead
  • 3) Multiple SE clients linked to multiple EE clients - possible
  • 4) Multiple EE clients linked to multiple EE clients - complex
  • 5) Multiple SE clients linked to single SE server - non EE
  • 6) Multiple SE clients linked to single EE server - in use
  • This model is the most promising and offers the least overhead. The SE clients will get and post work packets. If any user or admin needs to check the data they can do so via a browser based interface to the server.
  • 7) Multiple EE clients linked to single SE server - invalid
  • 8) Multiple EE clients linked to single EE server - possible

DI 2: Module Separation

  • All code should be separated by functionality
    • Model layer: JPA persistence Entitities/MappedSuperclasses/Embeddables should be in a separate model jar (with no persistence.xml)
      • The model layer ideally has 2 jars (one with entity interfaces), the other with the actual entities and their possible mapped superclasses and embeddables.
    • Business layer: The business objects (Session Beans) should be in a separate ejb jar and their interfaces (only) need to exported to clients (not the SSB implementation class). Why? because clients will only be interacting with the instrumented $proxy of the session bean - not the bean itself (which is a field of the server proxy)
      • The business layer ideally has 2 jars (one with the business interface classes) and one with the business implementation classes.
    • Presentation layer: The JSF managed and backing beans should be separate from both the model (entities) and the implementation (session beans) - these are delegates of the controller servlet (FacesServlet) - which implements the FrontController design pattern.

Stateful or Stateless Session Beans

  • Whether we use @Stateful' or @Stateless session beans will depend on whether we have a conversational message exchange between our clients and server. If our operation to get or post results to or from the server is atomic then a @Stateless session bean is sufficient. However, if our business process is conversational and spans multiple message calls or even multiple calls to multiple resources (using the XA 2-phase commit pattern) - then a @Stateful session bean is required.

DI 3: Remote RMI/EJB Communication type

Remote Session Beans on WebLogic 10.3.4.0

Remote Session Beans on GlassFish 3.1

DI 4: Type of client/server setup

  • 1) multiple SE clients communicate to a single EE server
  • 2) multiple EE clients communicate to a single EE server

Decision DI4:

  • We will be using 1) and only run a single EE server with multiple SE clients

DI 5: Limitations of BigInteger translation to BIGINT Database DataType

  • See 337036 : CS: TypeConverter conversion strategy for BigInteger 2^63 overflow because @Column override has no effect on NUMERIC field
  • The core of this application is the use of BigInteger Math package library which allows us to use arbitrary length integers during scalar computation. The underlying implementation of BigInteger is not the native long 64-bit datatype which would cause overflow. The ArrayList is used to represent the BigInteger digits.
  • There is an issue that occurs when an BigInteger is persisted to a database. Depending on the database (in this case Derby XA) and the JPA persistence provider (in this case EclipseLink - but we tested Hibernate as well) - the BigInteger will get truncated into a fixed size numeric field.
  • The issue is that any BigInteger that is greater than 63 bits cannot currently be stored in a NUMERIC field on a database without an overflow . This maximum number is represented by 10^19 or 9 quintillion.
  • We encounter these very large 10^19 or 9,223,372,036,854,775,808 numbers regularly in the following scenarios - we need a persistence strategy for users that wish to use them with JPA.
    • - scientific simulations
    • - cryptography
    • - amount of monthy internet traffic in bytes back in 2004 - an Exabyte
    • - nanosecond time calculations greater than 350 years
    • - factorials greater than 50 (or # of ways to order more than 50 objects)
  • In the above scenarios - scalar truncation must not be done by using FLOAT or DOUBLE types as the mantissa is also limited to 23 digits.
    /** Maximum BigInteger that can be stored in SQL field NUMERIC = 0x7fffffffffffffffL or
     * 2^63 or 10^19 or 9,223,372,036,854,775,808 or 9 Quintillion.
     * Numbers greater than this are encountered in scientific, cryptographic and nanosecond time sensitive calculations. 
     */
    private static final Long MAX_BIGINTEGER_IN_SQL = Long.MAX_VALUE;
  • This issue is independent of the JVM used whether 32 or 64 bit. The issue is related to the size of a Long in Java which is 64 bits.

Results DI5:

  • If we use JPA out of the box to persist a BigInteger that is larger than 64 bits like the maximum value for collatz path #88 with start 1,980,976,057,694,848,447 @61 bits and maximum 64,024,667,322,193,133,530,165,877,294,264,738,020 @125 bits - found by Tomás Oliveira e Silva and verified by Eric Roosendaal (which just happens to be the first maximum where the max bits is more than twice the start bits).
public static final BigInteger COLLATZ88 = BigInteger.valueOf(1980976057694848447L);
  • Client Logs:
C:\_experiment\org.eclipse.persistence.example.distributed.CollatzSE\bin>c:\jdk1.6.0\bin\java -cp .;../resource/wlfullclient.jar org.eclipse.persistence.example.distributed.collatz.presentation.SEClient xps435
_collatz: Context for xps435 : javax.naming.InitialContext@199f91c
_collatz: Remote Object: ClusterableRemoteRef(-6871653033103817620S:10.156.52.246:[7001,7001,-1,-1,-1,-1,-1]:base_domain:AdminServer [-6871653033103817620S:10.156.52.246:[7001,7001,-1,-1,-1,-1,-1]:base_domain:AdminServer/563])/563
_collatz: Narrowed Session Bean: ClusterableRemoteRef(-6871653033103817620S:10.156.52.246:[7001,7001,-1,-1,-1,-1,-1]:base_domain:AdminServer [-6871653033103817620S:10.156.52.246:[7001,7001,-1,-1,-1,-1,-1]:base_domain:AdminServer/563])/563
_collatz: process UnitOfWork: org.eclipse.persistence.example.distributed.collatz.model.UnitOfWork@454( id: 454) ID#454 1980976057694848448-1980976057694913983 from: xps435
_collatz: Proc cores:  2
_collatz: Interval:    65535
_collatz: Range:        1980976057694848448     to: 1980976057694913983
PM,65535,0,1980976057694848448,441      ,990488028847424224     ,15     ,
PM,65535,0,1980976057694848449,472      ,5942928173084545348    ,15     ,
PM,65535,0,1980976057694848450,578      ,18072027346986144304   ,31     2^63+,8848655310131368497
M,65535,0,1980976057694848462,578       ,19038843624808448404   ,31     2^63+,9815471587953672597
M,65535,0,1980976057694848463,578       ,20057382584160340696   ,47     2^63+,10834010547305564889
M,65535,0,1980976057694848478,578       ,28558265437212672832   ,47     2^63+,19334893400357897025
M,65535,0,1980976057694848479,578       ,30086073876240511288   ,47     2^63+,20862701839385735481
M,65535,0,1980976057694848487,578       ,274468915332352071232  ,62     2^63+,265245543295497295425
M,65535,0,1980976057694848671,578       ,564018617557005468928  ,78     2^63+,554795245520150693121
P,65535,0,1980976057694848795,653       ,385536583402371144736  ,109    2^63+,376313211365516368929
M,65535,0,1980976057694849106,653       ,594192453064170502480  ,140    2^63+,584969081027315726673
M,65535,0,1980976057694849199,653       ,1042124162902524644920 ,156    2^63+,1032900790865669869113
P,65535,0,1980976057694849826,808       ,9914389761744244528    ,234    2^63+,691017724889468721
M,65535,0,1980976057694849980,808       ,2967611385765393620872 ,265    2^63+,2958388013728538845065
M,65535,0,1980976057694850687,808       ,3008099293637365553848 ,344    2^63+,2998875921600510778041
M,65535,0,1980976057694850913,808       ,4512148940456048849092 ,375    2^63+,4502925568419194073285
M,65535,0,1980976057694851180,808       ,11421377005529375204776        ,406    2^63+,11412153633492520428969
M,65535,0,1980976057694854027,808       ,30457005348078377627776        ,703    2^63+,30447781976041522851969
M,65535,0,1980976057694870015,808       ,85563713241241454980420        ,2375   2^63+,85554489869204600204613
P,65535,0,1980976057694880475,883       ,1145817468019535170408 ,3500   2^63+,1136594095982680394601
P,65535,0,1980976057694891964,958       ,146548710408170692240  ,4734   2^63+,137325338371315916433
javax.ejb.EJBException: BEA1-040954FF03873057A4BD: Local Exception Stack:
Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.1.2.v20101206-r8635): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLDataException: The resulting value is outside the range for the data type BIGINT.
Error Code: -1
Call: UPDATE PARAMETERS SET MAXPATH = ?, MAXVALUE = ?, NEXTNUMBERTOSEARCH = ?, VERSION = ? WHERE ((ID = ?) AND (VERSION = ?))
        bind => [958, 85563713241241454980420, 1980976057694913983, 2, 451, 1]
Query: UpdateObjectQuery(org.eclipse.persistence.example.distributed.collatz.model.Parameters@451( id: 451))
        at org.eclipse.persistence.exceptions.DatabaseException.sqlException(DatabaseException.java:324)
        at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeDirectNoSelect(DatabaseAccessor.java:797)
        at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeNoSelect(DatabaseAccessor.java:863)
        at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.basicExecuteCall(DatabaseAccessor.java:583)
        at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeCall(DatabaseAccessor.java:526)
        at org.eclipse.persistence.internal.sessions.AbstractSession.executeCall(AbstractSession.java:980)
        at org.eclipse.persistence.internal.sessions.IsolatedClientSession.executeCall(IsolatedClientSession.java:131)
        at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.executeCall(DatasourceCallQueryMechanism.java:206)
        at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.executeCall
(DatasourceCallQueryMechanism.java:192)
        at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.updateObject(DatasourceCallQueryMechanism.java:747)
        at org.eclipse.persistence.internal.queries.StatementQueryMechanism.updateObject(StatementQueryMechanism.java:430)
        at org.eclipse.persistence.internal.queries.DatabaseQueryMechanism.updateObjectForWriteWithChangeSet(DatabaseQueryMechanism.java:1144)
        at org.eclipse.persistence.queries.UpdateObjectQuery.executeCommitWithChangeSet(UpdateObjectQuery.java:84)
        at org.eclipse.persistence.internal.queries.DatabaseQueryMechanism.executeWriteWithChangeSet(DatabaseQueryMechanism.java:290)
        at org.eclipse.persistence.queries.WriteObjectQuery.executeDatabaseQuery(WriteObjectQuery.java:58)
        at org.eclipse.persistence.queries.DatabaseQuery.execute(DatabaseQuery.java:740)
        at org.eclipse.persistence.queries.DatabaseQuery.executeInUnitOfWork(DatabaseQuery.java:643)
        at org.eclipse.persistence.queries.ObjectLevelModifyQuery.executeInUnitOfWorkObjectLevelModifyQuery(ObjectLevelModifyQuery.java:108)
        at org.eclipse.persistence.queries.ObjectLevelModifyQuery.executeInUnitOfWork(ObjectLevelModifyQuery.java:85)
        at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.internalExecuteQuery(UnitOfWorkImpl.java:2908)
        at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1291)
        at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1273)
        at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1233)
        at org.eclipse.persistence.internal.sessions.CommitManager.commitChangedObjectsForClassWithChangeSet(CommitManager.java:265)
        at org.eclipse.persistence.internal.sessions.CommitManager.commitAllObjectsWithChangeSet(CommitManager.java:128)
        at org.eclipse.persistence.internal.sessions.AbstractSession.writeAllObjectsWithChangeSet(AbstractSession.java:3348)
        at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabase(UnitOfWorkImpl.java:1422)
        at org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.commitToDatabase(RepeatableWriteUnitOfWork.java:610)
        at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabaseWithChangeSet(UnitOfWorkImpl.java:1527)
        at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.issueSQLbeforeCompletion(UnitOfWorkImpl.java:3181)
        at org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.issueSQLbeforeCompletion(RepeatableWriteUnitOfWork.java:332)
        at org.eclipse.persistence.transaction.AbstractSynchronizationListener.beforeCompletion(AbstractSynchronizationListener.java:157)
        at org.eclipse.persistence.transaction.JTASynchronizationListener.beforeCompletion(JTASynchronizationListener.java:68)
        at weblogic.transaction.internal.ServerSCInfo.doBeforeCompletion(ServerSCInfo.java:1239)
        at weblogic.transaction.internal.ServerSCInfo.callBeforeCompletions(ServerSCInfo.java:1214)
        at weblogic.transaction.internal.ServerSCInfo.startPrePrepareAndChain(ServerSCInfo.java:116)
        at weblogic.transaction.internal.ServerTransactionImpl.localPrePrepareAndChain(ServerTransactionImpl.java:1316)
        at weblogic.transaction.internal.ServerTransactionImpl.globalPrePrepare(ServerTransactionImpl.java:2132)
        at weblogic.transaction.internal.ServerTransactionImpl.internalCommit(ServerTransactionImpl.java:272)
        at weblogic.transaction.internal.ServerTransactionImpl.commit(ServerTransactionImpl.java:239)
        at weblogic.ejb.container.internal.BaseRemoteObject.postInvoke1(BaseRemoteObject.java:625)
        at weblogic.ejb.container.internal.StatelessRemoteObject.postInvoke1(StatelessRemoteObject.java:49)
        at weblogic.ejb.container.internal.BaseRemoteObject.__WL_postInvokeTxRetry(BaseRemoteObject.java:444)
        at weblogic.ejb.container.internal.SessionRemoteMethodInvoker.invoke(SessionRemoteMethodInvoker.java:53)
        at org.eclipse.persistence.example.distributed.collatz.business.CollatzFacade_of6sps_CollatzFacadeRemoteImpl.postUnitOfWork(Unknown Source)
        at org.eclipse.persistence.example.distributed.collatz.business.CollatzFacade_of6sps_CollatzFacadeRemoteImpl_WLSkel.invoke(Unknown Source)
        at weblogic.rmi.internal.BasicServerRef.invoke(BasicServerRef.java:667)
        at weblogic.rmi.cluster.ClusterableServerRef.invoke(ClusterableServerRef.java:230)
        at weblogic.rmi.internal.BasicServerRef$1.run(BasicServerRef.java:522)
        at weblogic.security.acl.internal.AuthenticatedSubject.doAs(AuthenticatedSubject.java:363)
        at weblogic.security.service.SecurityManager.runAs(SecurityManager.java:146)
        at weblogic.rmi.internal.BasicServerRef.handleRequest(BasicServerRef.java:518)
        at weblogic.rmi.internal.wls.WLSExecuteRequest.run(WLSExecuteRequest.java:118)
        at weblogic.work.ExecuteThread.execute(ExecuteThread.java:207)
        at weblogic.work.ExecuteThread.run(ExecuteThread.java:176)
Caused by: java.sql.SQLDataException: The resulting value is outside the range for the data type BIGINT.
        at org.apache.derby.client.am.SQLExceptionFactory40.getSQLException(Unknown Source)
        at org.apache.derby.client.am.SqlException.getSQLException(Unknown Source)
        at org.apache.derby.client.am.PreparedStatement.executeUpdate(Unknown Source)
        at weblogic.jdbc.wrapper.PreparedStatement.executeUpdate(PreparedStatement.java:172)
        at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeDirectNoSelect(DatabaseAccessor.java:788)
        ... 53 more
Caused by: org.apache.derby.client.am.SqlException: The resulting value is outside the range for the data type BIGINT.
        at org.apache.derby.client.am.Statement.completeExecute(Unknown Source)
        at org.apache.derby.client.net.NetStatementReply.parseEXCSQLSTTreply(Unknown Source)
        at org.apache.derby.client.net.NetStatementReply.readExecute(Unknown Source)
        at org.apache.derby.client.net.StatementReply.readExecute(Unknown Source)
        at org.apache.derby.client.net.NetPreparedStatement.readExecute_(Unknown Source)
        at org.apache.derby.client.am.PreparedStatement.readExecute(Unknown Source)
        at org.apache.derby.client.am.PreparedStatement.flowExecute(Unknown Source)
        at org.apache.derby.client.am.PreparedStatement.executeUpdateX(Unknown Source)
        ... 56 more
; nested exception is:
        Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.1.2.v20101206-r8635): org.eclipse.persistence.exceptions.DatabaseException
  • Server Logs
[EL Fine]: 2011-02-10 16:29:58.134--ClientSession(14259188)--Connection(9575331)--Thread(Thread[[ACTIVE] ExecuteThread: '7' for queue: 'weblogic.kernel.Default (self-tuning)',5,Pooled Threads])--UPDATE PARAMETERS SET MAXPATH = ?, MAXVALUE = ?, NEXTNUMBERTOSEARCH = ?, VERSION = ? WHERE ((ID = ?) AND (VERSION = ?))
	bind => [958, 85563713241241454980420, 1980976057694913983, 2, 451, 1]
[EL Fine]: 2011-02-10 16:29:58.149--ClientSession(14259188)--Thread(Thread[[ACTIVE] ExecuteThread: '7' for queue: 'weblogic.kernel.Default (self-tuning)',5,Pooled Threads])--VALUES(1)
[EL Warning]: 2011-02-10 16:29:58.149--UnitOfWork(10050911)--Thread(Thread[[ACTIVE] ExecuteThread: '7' for queue: 'weblogic.kernel.Default (self-tuning)',5,Pooled Threads])--Local Exception Stack: 
Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.1.2.v20101206-r8635): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLDataException: The resulting value is outside the range for the data type BIGINT.
Error Code: -1
Call: UPDATE PARAMETERS SET MAXPATH = ?, MAXVALUE = ?, NEXTNUMBERTOSEARCH = ?, VERSION = ? WHERE ((ID = ?) AND (VERSION = ?))
	bind => [958, 85563713241241454980420, 1980976057694913983, 2, 451, 1]
Query: UpdateObjectQuery(org.eclipse.persistence.example.distributed.collatz.model.Parameters@451( id: 451))
	at org.eclipse.persistence.exceptions.DatabaseException.sqlException(DatabaseException.java:324)
	at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeDirectNoSelect(DatabaseAccessor.java:797)
  • Hibernate JPA logs
INFO: New max value: 1414236446719942480
INFO: Hibernate: update Parameters set bestIterationsPerSecond=?, globalDuration=?, globalStartTimestamp=?, maxPath=?, maxValue=?, nextNumberToSearch=?, partitionLength=?, version=? where id=? and version=?
WARNING: SQL Error: -1, SQLState: 22003
SEVERE: The resulting value is outside the range for the data type DECIMAL/NUMERIC(19,2).
SEVERE: Could not synchronize database state with session
org.hibernate.exception.DataException: could not update: [org.dataparallel.collatz.business.Parameters#32768]
        at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:77)
        at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43)
        at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2425)
        at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:2307)
        at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2607)
        at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:92)
        at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:250)
        at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:234)
        at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:142)
        at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:298)
        at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27)
        at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1000)
        at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:338)
        at org.hibernate.ejb.AbstractEntityManagerImpl$1.beforeCompletion(AbstractEntityManagerImpl.java:523)
        at com.sun.enterprise.transaction.JavaEETransactionImpl.commit(JavaEETransactionImpl.java:412)
        at com.sun.enterprise.transaction.JavaEETransactionManagerSimplified.commit(JavaEETransactionManagerSimplified.java:837)
        at com.sun.ejb.containers.BaseContainer.completeNewTx(BaseContainer.java:5040)
        at com.sun.ejb.containers.BaseContainer.postInvokeTx(BaseContainer.java:4805)
        at com.sun.ejb.containers.BaseContainer.postInvoke(BaseContainer.java:2004)
        at com.sun.ejb.containers.BaseContainer.postInvoke(BaseContainer.java:1955)
        at com.sun.ejb.containers.EJBObjectInvocationHandler.invoke(EJBObjectInvocationHandler.java:208)
        at com.sun.ejb.containers.EJBObjectInvocationHandlerDelegate.invoke(EJBObjectInvocationHandlerDelegate.java:75)
        at $Proxy199.postUnitOfWork(Unknown Source)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at com.sun.corba.ee.impl.presentation.rmi.ReflectiveTie.dispatchToMethod(ReflectiveTie.java:146)
        at com.sun.corba.ee.impl.presentation.rmi.ReflectiveTie._invoke(ReflectiveTie.java:176)
        at com.sun.corba.ee.impl.protocol.CorbaServerRequestDispatcherImpl.dispatchToServant(CorbaServerRequestDispatcherImpl.java:682)
        at com.sun.corba.ee.impl.protocol.CorbaServerRequestDispatcherImpl.dispatch(CorbaServerRequestDispatcherImpl.java:216)
        at com.sun.corba.ee.impl.protocol.CorbaMessageMediatorImpl.handleRequestRequest(CorbaMessageMediatorImpl.java:1841)
        at com.sun.corba.ee.impl.protocol.CorbaMessageMediatorImpl.handleRequest(CorbaMessageMediatorImpl.java:1695)
        at com.sun.corba.ee.impl.protocol.CorbaMessageMediatorImpl.handleInput(CorbaMessageMediatorImpl.java:1078)
        at com.sun.corba.ee.impl.protocol.giopmsgheaders.RequestMessage_1_2.callback(RequestMessage_1_2.java:221)
        at com.sun.corba.ee.impl.protocol.CorbaMessageMediatorImpl.handleRequest(CorbaMessageMediatorImpl.java:797)
        at com.sun.corba.ee.impl.protocol.CorbaMessageMediatorImpl.dispatch(CorbaMessageMediatorImpl.java:561)
        at com.sun.corba.ee.impl.protocol.CorbaMessageMediatorImpl.doWork(CorbaMessageMediatorImpl.java:2558)
        at com.sun.corba.ee.impl.orbutil.threadpool.ThreadPoolImpl$WorkerThread.performWork(ThreadPoolImpl.java:492)
        at com.sun.corba.ee.impl.orbutil.threadpool.ThreadPoolImpl$WorkerThread.run(ThreadPoolImpl.java:528)
Caused by: java.sql.SQLDataException: The resulting value is outside the range for the data type DECIMAL/NUMERIC(19,2).
        at org.apache.derby.client.am.SQLExceptionFactory40.getSQLException(Unknown Source)
        at org.apache.derby.client.am.SqlException.getSQLException(Unknown Source)
        at org.apache.derby.client.am.PreparedStatement.executeUpdate(Unknown Source)
        at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2407)
        ... 37 more
Caused by: org.apache.derby.client.am.SqlException: The resulting value is outside the range for the data type DECIMAL/NUMERIC(19,2).

Analysis DI5:

  • I suspect that it would be simplest to just convert the BigInteger to a string (VARCHAR2) and convert back when reading from the database. There may be a more efficient algorithm that involves partitioning or variable length scalar fields as well.
  • The reality is that the DDL generation between EclipseLink and Hibernate are different. DDL generation in general should not be used for production. It would be better to fine tune the table creation myself.
  • The DDL generation should pick up the column length annotation attribute though
@Entity
public class Parameters implements Serializable {
    @Column(name="maxValue", length=512)    
    private BigInteger maxValue;

TypeConverter

  • We will be using a @TypeConverter which is provided beyond the JPA specification using native EclipseLink ORM.
  • Use of a TypeConverter (not an ObjectTypeConverter) may be one option. We would map the BigInteger type to a String which could be stored in a column that is larger than the current 128 bit (16 byte) lenght of NUMERIC. This would eventually hit a maximum of 1-2K, where if we represented each bit as a 0 or 1 byte we could at least represent 1024 bits with this strategy.
@Entity
@TypeConverters({@TypeConverter(name="BigIntegerToString",dataType=String.class,objectType=BigInteger.class)})
public class Parameters implements Serializable {
    private static final long serialVersionUID = -1979843739878183696L;
    @Column(name="maxValue", nullable=false, length=512)
    @Convert("BigIntegerToString")
    private BigInteger maxValue;
  • The following works with @TypeConverter or either @TypeConverters location on the class or attribute
@Entity
@TypeConverter(name="BigIntegerToString",dataType=String.class,objectType=BigInteger.class)
public class UnitOfWork implements Serializable {
    @Column(nullable=false, length=1024)
    @Convert("BigIntegerToString")
    private BigInteger initial;
    @Column(nullable=false, length=1024)
    @Convert("BigIntegerToString")
    private BigInteger extent;
  • For Derby 10.5 generates VARCHAR2 for fields up to 512 or VARCHAR for 1024+
CREATE TABLE UNITOFWORK (
        ID BIGINT NOT NULL,
        EXTENT VARCHAR(1024) NOT NULL,
        INITIAL VARCHAR(1024) NOT NULL,
        MAXPATH VARCHAR(1024) NOT NULL,
        MAXVALUE VARCHAR(1024) NOT NULL,
        VERSION BIGINT,
  • We are now able to store integers around 2^124
[EL Fine]: 2011-02-15
14:21:58.311--ClientSession(13290230)--Connection(11308642)--Thread(Thread[[ACTIVE]
ExecuteThread: '20' for queue: 'weblogic.kernel.Default (self-tuning)',5,Pooled
Threads])--INSERT INTO UNITOFWORK (ID, ENDTIMESTAMP, EXTENT, INITIAL, MAXPATH,
MAXVALUE, RETRIES, STARTTIMESTAMP, VERSION, KNOWNMAX_ID, KNOWNPATH_ID,
PROCESSOR_ID) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    bind => [27, null, 21267647932558653966460912964487610368,
21267647932558653966460912964486561793, 1488,
15728073752807962983290439751148709753444136, 0, 1297795918311, 1, null, null,
1]

DI 6: EAR Redeploy should not affect remote clients

  • If the server application is temporarily down due to a redeploy - it should not affect clients.
  • The fix is to catch the NoSuchObjectException and perform a series of timed re-posts to the session bean.

Analysis DI6:

  • Error on the remote client is as follows when the server app is hot-redeployed (without clustering) at the same time as the client is pushing a data post to the server.
java.rmi.NoSuchObjectException: The object identified by: '312' could not be found.  Either it was has not been exported or it has been collected by the distributed garbage collector.
        at weblogic.rjvm.ResponseImpl.unmarshalReturn(ResponseImpl.java:234)
        at weblogic.rmi.cluster.ClusterableRemoteRef.invoke(ClusterableRemoteRef.java:348)
        at weblogic.rmi.cluster.ClusterableRemoteRef.invoke(ClusterableRemoteRef.java:259)
        at org.eclipse.persistence.example.distributed.collatz.business.CollatzFacade_of6sps_CollatzFacadeRemoteImpl_1034_WLStub.postUnitOfWork(Unknown Source)
        at sun.reflect.GeneratedMethodAccessor5.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at weblogic.ejb.container.internal.RemoteBusinessIntfProxy.invoke(RemoteBusinessIntfProxy.java:85)
        at $Proxy0.postUnitOfWork(Unknown Source)
        at org.eclipse.persistence.example.distributed.collatz.presentation.SEClient.processUnitOfWork(SEClient.java:216)
  • Solved by a finite number of repeated lookup operations - without a wait
  • Action: A redeploy with an interval change from 10 to 11 bits
_collatz: Remote Object: ClusterableRemoteRef(-2557087906773732497S:10.156.52.246:[7001,7001,-1,-1,-1,-1,-1]:base_domain:AdminServer [-2557087906773732497S:10.156.52.246:[7001,7001,-1,-1,-1,-1,-1]:base_domain:AdminServer/314])/314
_collatz: process UnitOfWork: org.eclipse.persistence.example.distributed.collatz.model.UnitOfWork@8944( id: 8944) ID#8944 15125507-15126530 from: xps435
_collatz: Proc cores:  2
_collatz: Interval:    1023
_collatz: Range:        15125507        to: 15126530
While trying to lookup 'ejb.CollatzFacade#org.eclipse.persistence.example.distributed.collatz.business.CollatzFacadeRemote' didn't find subcontext 'CollatzFacade#org'. Resolved 'ejb'
_collatz: retry session bean lookup - possible redeploy in progress on central server: 0
While trying to lookup 'ejb.CollatzFacade#org.eclipse.persistence.example.distributed.collatz.business.CollatzFacadeRemote' didn't find subcontext 'CollatzFacade#org'. Resolved 'ejb'
_collatz: retry session bean lookup - possible redeploy in progress on central server: 1
While trying to lookup 'ejb.CollatzFacade#org.eclipse.persistence.example.distributed.collatz.business.CollatzFacadeRemote' didn't find subcontext 'CollatzFacade#org'. Resolved 'ejb'
....
_collatz: retry session bean lookup - possible redeploy in progress on central server: 32
While trying to lookup 'ejb.CollatzFacade#org.eclipse.persistence.example.distributed.collatz.business.CollatzFacadeRemote' didn't find subcontext 'example'. Resolved 'ejb.CollatzFacade#org.eclipse.persistence'
_collatz: retry session bean lookup - possible redeploy in progress on central server: 33
_collatz: Remote Object: ClusterableRemoteRef(-2557087906773732497S:10.156.52.246:[7001,7001,-1,-1,-1,-1,-1]:base_domain:AdminServer [-2557087906773732497S:10.156.52.246:[7001,7001,-1,-1,-1,-1,-1]:base_domain:AdminServer/322])/322
_collatz: process UnitOfWork: org.eclipse.persistence.example.distributed.collatz.model.UnitOfWork@8953( id: 8953) ID#8953 15126531-15128578 from: xps435
_collatz: Proc cores:  2
_collatz: Interval:    2047
_collatz: Range:        15126531        to: 15128578
_collatz: Remote Object: ClusterableRemoteRef(-2557087906773732497S:10.156.52.246:[7001,7001,-1,-1,-1,-1,-1]:base_domain:AdminServer [-2557087906773732497S:10.156.52.246:[7001,7001,-1,-1,-1,-1,-1]:base_domain:AdminServer/322])/322
  • With a 1000ms wait
  • Action: A redeploy with an interval change from 11 to 12 bits
_collatz: process UnitOfWork: org.eclipse.persistence.example.distributed.collatz.model.UnitOfWork@10408( id: 10408) ID#10408 16117763-16119810 from: xps435
_collatz: Proc cores:  2
_collatz: Interval:    2047
_collatz: Range:        16117763        to: 16119810
While trying to lookup 'ejb.CollatzFacade#org.eclipse.persistence.example.distributed.collatz.business.CollatzFacadeRemote' didn't find subcontext 'CollatzFacade#org'. Resolved 'ejb'
_collatz: retry session bean lookup - possible redeploy in progress on central server: 0
_collatz: Remote Object: ClusterableRemoteRef(-2557087906773732497S:10.156.52.246:[7001,7001,-1,-1,-1,-1,-1]:base_domain:AdminServer [-2557087906773732497S:10.156.52.246:[7001,7001,-1,-1,-1,-1,-1]:base_domain:AdminServer/331])/331
_collatz: process UnitOfWork: org.eclipse.persistence.example.distributed.collatz.model.UnitOfWork@10453( id: 10453) ID#10453 16119811-16123906 from: xps435
_collatz: Proc cores:  2
_collatz: Interval:    4095
  • We also need to handle the following exception on redeploy
_collatz: results sent to server after 390 ms
javax.ejb.EJBException: [WorkManager:002917]Enqueued Request belonging to WorkManager default, application org.eclipse.persistence.example.distributed.CollatzEAR is cancelled as the WorkManager is shutdown; nested exception is: weblogic.work.WorkRejectedException: [WorkManager:002917]Enqueued Request belonging to WorkManager default, application org.eclipse.persistence.example.distributed.CollatzEAR is cancelled as the WorkManager is shutdown
weblogic.work.WorkRejectedException: [WorkManager:002917]Enqueued Request belonging to WorkManager default, application org.eclipse.persistence.example.distributed.CollatzEAR is cancelled as the WorkManager is shutdown
        at weblogic.rjvm.ResponseImpl.unmarshalReturn(ResponseImpl.java:234)
        at weblogic.rmi.cluster.ClusterableRemoteRef.invoke(ClusterableRemoteRef.java:348)
        at weblogic.rmi.cluster.ClusterableRemoteRef.invoke(ClusterableRemoteRef.java:259)
        at org.eclipse.persistence.example.distributed.collatz.business.CollatzFacade_of6sps_CollatzFacadeRemoteImpl_1034_WLStub.requestUnitOfWork(Unknown Source)
        at sun.reflect.GeneratedMethodAccessor12.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at weblogic.ejb.container.internal.RemoteBusinessIntfProxy.invoke(RemoteBusinessIntfProxy.java:85)
        at $Proxy0.requestUnitOfWork(Unknown Source)
        at org.eclipse.persistence.example.distributed.collatz.presentation.SEClient.processUnitOfWork(SEClient.java:161)
        at org.eclipse.persistence.example.distributed.collatz.presentation.SEClient.main(SEClient.java:224)
javax.ejb.EJBException: [WorkManager:002917]Enqueued Request belonging to WorkManager default, application org.eclipse.persistence.example.distributed.CollatzEAR is cancelled as the WorkManager is shutdown; nested exception is: weblogic.work.WorkRejectedException: [WorkManager:002917]Enqueued Request belonging to WorkManager default, application org.eclipse.persistence.example.distributed.CollatzEAR is cancelled as the WorkManager is shutdown
        at weblogic.ejb.container.internal.RemoteBusinessIntfProxy.unwrapRemoteException(RemoteBusinessIntfProxy.java:124)
        at weblogic.ejb.container.internal.RemoteBusinessIntfProxy.invoke(RemoteBusinessIntfProxy.java:96)
        at $Proxy0.requestUnitOfWork(Unknown Source)
        at org.eclipse.persistence.example.distributed.collatz.presentation.SEClient.processUnitOfWork(SEClient.java:161)
        at org.eclipse.persistence.example.distributed.collatz.presentation.SEClient.main(SEClient.java:224)
Caused by: weblogic.work.WorkRejectedException: [WorkManager:002917]Enqueued Request belonging to WorkManager default, application org.eclipse.persistence.example.distributed.CollatzEAR is cancelled as the WorkManager is shutdown
        at weblogic.rjvm.ResponseImpl.unmarshalReturn(ResponseImpl.java:234)
        at weblogic.rmi.cluster.ClusterableRemoteRef.invoke(ClusterableRemoteRef.java:348)
        at weblogic.rmi.cluster.ClusterableRemoteRef.invoke(ClusterableRemoteRef.java:259)
        at org.eclipse.persistence.example.distributed.collatz.business.CollatzFacade_of6sps_CollatzFacadeRemoteImpl_1034_WLStub.requestUnitOfWork(Unknown Source)
        at sun.reflect.GeneratedMethodAccessor12.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at weblogic.ejb.container.internal.RemoteBusinessIntfProxy.invoke(RemoteBusinessIntfProxy.java:85)
        ... 3 more

DI 7: Strategy for handling expected OptimisticLockException during high throughput concurrent traffic

  • This is one of our major design issues to overcome.
  • If I reduce the interval for each UnitOfWork from a comfortable 16 to 22 bits down to 8 bits (256 searches) this increases requests to the server to about 5 per second. If we run more than one client we almost immediately get an OptimisticLockException when one of the clients tries to overwrite shared memory (in the Parameters singleton entity). We expect this because of the concurrent nature of our distributed application. We will do a read, evaluate the change compared to our unsaved changes and retry if needed. We may need to do this a couple times - as the window between this manual 2-phase commit operation still has a small window of unmanaged concurrency between the read and write operations.
  • How do we test for this?
    • On a separate machine or two - set the search interval very low (like 18 or 16 bits) so we generate request at more than 1 per second.
    • On the server - set it to debug in Eclipse and set breakpoint on a client also running from eclipse in the catch block.
    • Now when the remote servers hammer the WebLogic server, eventually the SE client in eclipse will hit the breakpoint where it usally would crash on an unhandled OptimisticLockException.

Client Log Exception

_collatz: Remote Object: ClusterableRemoteRef(1326838513503838804S:10.156.52.246:[7001,7001,-1,-1,-1,-1,-1]:base_domain:AdminServer [1326838513503838804S:10.156.52.246:[7001,7001,-1,-1,-1,-1,-1]:base_domain:AdminServer/322])/322
javax.ejb.EJBException: BEA1-21603518C2783057A4BD: javax.persistence.OptimisticLockException: Exception [EclipseLink-5006] (Eclipse Persistence Services - 2.3.0.qualifier): org.eclipse.persistence.exceptions.OptimisticLockException
Exception Description: The object [org.eclipse.persistence.example.distributed.collatz.model.Parameters@2( id: 2)] cannot be updated because it has changed or been deleted since it was last read.
Class> org.eclipse.persistence.example.distributed.collatz.model.Parameters Primary Key> 2
        at org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.commitToDatabase(RepeatableWriteUnitOfWork.java:623)
        at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabaseWithChangeSet(UnitOfWorkImpl.java:1486)
        at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.issueSQLbeforeCompletion(UnitOfWorkImpl.java:3109)
        at org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.issueSQLbeforeCompletion(RepeatableWriteUnitOfWork.java:331)
        at org.eclipse.persistence.transaction.AbstractSynchronizationListener.beforeCompletion(AbstractSynchronizationListener.java:157)
        at org.eclipse.persistence.transaction.JTASynchronizationListener.beforeCompletion(JTASynchronizationListener.java:68)
        at weblogic.transaction.internal.ServerSCInfo.doBeforeCompletion(ServerSCInfo.java:1239)
        at weblogic.transaction.internal.ServerSCInfo.callBeforeCompletions(ServerSCInfo.java:1214)
        at weblogic.transaction.internal.ServerSCInfo.startPrePrepareAndChain(ServerSCInfo.java:116)
        at weblogic.transaction.internal.ServerTransactionImpl.localPrePrepareAndChain(ServerTransactionImpl.java:1316)
        at weblogic.transaction.internal.ServerTransactionImpl.globalPrePrepare(ServerTransactionImpl.java:2132)
        at weblogic.transaction.internal.ServerTransactionImpl.internalCommit(ServerTransactionImpl.java:272)
        at weblogic.transaction.internal.ServerTransactionImpl.commit(ServerTransactionImpl.java:239)
        at weblogic.ejb.container.internal.BaseRemoteObject.postInvoke1(BaseRemoteObject.java:625)
        at weblogic.ejb.container.internal.StatelessRemoteObject.postInvoke1(StatelessRemoteObject.java:49)
        at weblogic.ejb.container.internal.BaseRemoteObject.__WL_postInvokeTxRetry(BaseRemoteObject.java:444)
        at weblogic.ejb.container.internal.SessionRemoteMethodInvoker.invoke(SessionRemoteMethodInvoker.java:53)
        at org.eclipse.persistence.example.distributed.collatz.business.CollatzFacade_of6sps_CollatzFacadeRemoteImpl.postUnitOfWork(Unknown Source)
        at org.eclipse.persistence.example.distributed.collatz.business.CollatzFacade_of6sps_CollatzFacadeRemoteImpl_WLSkel.invoke(Unknown Source)
        at weblogic.rmi.internal.BasicServerRef.invoke(BasicServerRef.java:667)
        at weblogic.rmi.cluster.ClusterableServerRef.invoke(ClusterableServerRef.java:230)

DI7: Analysis

  • It would be better that we handle this on the server in the session bean. We can then leverage this single solution regardless of what client we use (RMI/EJB, WebService, JAX-RS).
 

DI 8: Variable Partition between Different Client Capabilities

  • We will attempt to use a homogeneos set of distributed processors, however we will need to accomidate processing nodes with a variance of capabilities.
  • The collatz problem is well suited to parallization because of the relative independence of the calculations on individual sequences. However, if we wish to optimize the algorith so we can reduce the calculation times by an order of magnitude - then we will need to use the symmetry of previos calculations.
  • Example: a large proportion of numbers greater than 27 will contain the 27:110:9232 record (27=start, 110=sequence path lenght, 9232=maximum value). One optimation would be do abort sequences that would not reach a max path or max value if their current path:max was merged with 27:110:9292 if they hit 27 at any time in their sequence.
  • Therefore, we will need an evaluation step for new nodes so that we can distribute the appropriate # of UnitOfWork packets so that all the processors work the same amount of time.
  • Q)Why manage calculation time?
  • A)

DI9: Force binary compatibility with Model changes via the serialVersionUID

javax.ejb.EJBException: ; nested exception is:
        java.rmi.UnmarshalException: Incoming message header or abbreviation processing failed ; nested exception is:
        java.io.InvalidClassException: org.eclipse.persistence.example.distributed.collatz.model.ActiveProcessor; local class incompatible:
stream classdesc serialVersionUID = 6472979075266547411, local class serialVersionUID = 1; nested exception is: java.rmi.UnmarshalException:
 Incoming message header or abbreviation processing failed ; nested exception is:
        java.io.InvalidClassException: org.eclipse.persistence.example.distributed.collatz.model.ActiveProcessor; local class incompatible:
stream classdesc serialVersionUID = 6472979075266547411, local class serialVersionUID = 1
        at weblogic.ejb.container.internal.RemoteBusinessIntfProxy.unwrapRemoteException(RemoteBusinessIntfProxy.java:121)
        at weblogic.ejb.container.internal.RemoteBusinessIntfProxy.invoke(RemoteBusinessIntfProxy.java:96)
        at $Proxy0.requestUnitOfWork(Unknown Source)
        at org.eclipse.persistence.example.distributed.collatz.presentation.SEClient.processUnitOfWork(SEClient.java:214)
        at org.eclipse.persistence.example.distributed.collatz.presentation.SEClient.main(SEClient.java:275)
Caused by: java.rmi.UnmarshalException: Incoming message header or abbreviation processing failed ; nested exception is:
        java.io.InvalidClassException: org.eclipse.persistence.example.distributed.collatz.model.ActiveProcessor; local class incompatible:
stream classdesc serialVersionUID = 6472979075266547411, local class serialVersionUID = 1
        at weblogic.rjvm.MsgAbbrevJVMConnection.dispatch(MsgAbbrevJVMConnection.java:507)
        at weblogic.rjvm.t3.MuxableSocketT3.dispatch(MuxableSocketT3.java:330)

DI10:Entity search for WebLogic should not require <class> elements when <jar-file> specified

  • There may be an issue with entity search in WebLogic 10.3.4.0 when using a separate <jar-file> for entities.
  • On Glassfish 3, specifying only <jar-file> is sufficient, on WebLogic we need to also specify <class> - this should not be necessary for a managed @PersistenceContext.
  • In my Java EE 5 projects I always run with explicit <class> elements - whether i am using a managed @PersistenceContext or an un-managed @PersistenceUnit. I have 2 nearly identical projects that use an external jar

to contain the entity classes - that are referenced from a persistence.xml in the separate ejb-jar file.

  • I turned off <class> elements and deferred to <jar-file> and/or manifest entry - as instructed by the Java EE 5 spec -and our own "Pro JPA 2 p.413". Note: I do not directly reference the EM from the WAR - so I don't need a ref

there.

  • On GlassFish V3 via NetBeans 6.9 I run fine with the following (no class elements as per spec = OK)
    <jar-file>CollatzModel-jar.jar</jar-file>
    <!--class>org.dataparallel.collatz.business.ActiveProcessor</class>
    <class>org.dataparallel.collatz.business.CollatzRecord</class>
    <class>org.dataparallel.collatz.business.Parameters</class>
    <class>org.dataparallel.collatz.business.UnitOfWork</class-->
    <!--exclude-unlisted-classes>false</exclude-unlisted-classes-->
FINER: Class [org.dataparallel.collatz.business.CollatzRecord] registered to be processed by weaver.
  • However on WebLogic 10.3.4, I have tried everything, relative paths ../lib, /lib, lib etc (there seems to be some difference on whether to state the path to the default EAR/lib dir) and I can only get WebLogic to find the entities if i also list them as class elements. The jar is being found evidently on the classpath - it is just that the entities are not processed unless also listed - which should not be necessary as they are annotated. Need to check an older JPA 1.0 server that does not use the patch jar.
 
<jar-file>org.eclipse.persistence.example.distributed.CollatzModel.jar</jar-file>
  • These should not be required
 
<class>org.eclipse.persistence.example.distributed.collatz.model.ActiveProcessor</class>
<class>org.eclipse.persistence.example.distributed.collatz.model.CollatzRecord</class>
<class>org.eclipse.persistence.example.distributed.collatz.model.Parameters</class>
<class>org.eclipse.persistence.example.distributed.collatz.model.UnitOfWork</class>
    <!--exclude-unlisted-classes>false</exclude-unlisted-classes-->
  • with <class> elements
[EL Finer]: 2011-03-08 21:47:06.796--ServerSession(5694614)--Thread(Thread[[STANDBY] ExecuteThread: '3' for queue: 'weblogic.kernel.Default (self-tuning)',5,Pooled Threads])--Class [org.eclipse.persistence.example.distributed.collatz.model.CollatzRecord] registered to be processed by weaver.
  • I will need to get the source for the WLS patch jar com.oracle.jpa2support_1.0.0.0_2.0.jar
  • It may be my MANIFEST - but likely not because when entity names are specified - they are found and processed in the jar lib.
  • Both of the calls [getManagedClassNames() and getJarFileUrls()] to the server class PersistenceUnitInfoImpl return nothing. A later excludeUnlistedClasses() call is expected to be empty.
Oracle WebLogic Server 11gR1 PatchSet 3 r20110115 [base_domain] [Oracle
WebLogic Server]   
    Java HotSpot(TM) Client VM[localhost:8453]   
        Daemon Thread [[ACTIVE] ExecuteThread: '5' for queue:
'weblogic.kernel.Default (self-tuning)'] (Running)   
        Daemon Thread [[STANDBY] ExecuteThread: '4' for queue:
'weblogic.kernel.Default (self-tuning)'] (Suspended)   
            PersistenceUnitInfoImpl.getManagedClassNames() line: 19 [local
variables unavailable]   
            MetadataProcessor.initPersistenceUnitClasses() line: 261   
            MetadataProcessor.processEntityMappings() line: 470   
Oracle WebLogic Server 11gR1 PatchSet 3 r20110115 [base_domain] [Oracle
WebLogic Server]   
    Java HotSpot(TM) Client VM[localhost:8453]   
        Daemon Thread [[ACTIVE] ExecuteThread: '5' for queue:
'weblogic.kernel.Default (self-tuning)'] (Running)   
        Daemon Thread [[STANDBY] ExecuteThread: '4' for queue:
'weblogic.kernel.Default (self-tuning)'] (Suspended)   
            PersistenceUnitInfoImpl.getJarFileUrls() line: 19 [local variables
unavailable]   
            MetadataProcessor.initPersistenceUnitClasses() line: 264   
            MetadataProcessor.processEntityMappings() line: 470   
  • However If I specify class names then your PersistenceUnitInfoImpl.getManagedClassNames() returns the entity list specified in persistence.xml back to us.
classNames    ArrayList<E>  (id=467)    
    elementData    Object[10]  (id=469)    
        [0]   
"org.eclipse.persistence.example.distributed.collatz.model.ActiveProcessor"
(id=472)    
        [1]   
"org.eclipse.persistence.example.distributed.collatz.model.CollatzRecord"
(id=473)    
        [2]   
"org.eclipse.persistence.example.distributed.collatz.model.Parameters" (id=474) 
        [3]   
"org.eclipse.persistence.example.distributed.collatz.model.UnitOfWork" (id=475) 
    modCount    1    
    size    4    
  • in
        PersistenceUnitInfo persistenceUnitInfo =
m_project.getPersistenceUnitInfo();
        List<String> classNames = new ArrayList<String>();
 
        // Add all the <class> specifications.
        classNames.addAll(persistenceUnitInfo.getManagedClassNames());

DI 11: Asynchronous AJAX Client Architecture should not directly use JTA @Stateless Session Bean

  • I connected a brute force AJAX client in the form of a JSP page before getting into the AJAX support that ships with JSF 2.0 and quickly realized that I am hammering the server on each request possibly unnecessarily.

IMG 2629 collatz active ajax client on blackberry and pc 480.jpg

<script language="JavaScript" type="text/javascript">
 function FactoryXMLHttpRequest() {
	   if(window.XMLHttpRequest) {
		   // Mozilla
		   return new XMLHttpRequest();
...
 function Ajax_call(url, elementId) {
	 var instance = this;
...
        out.println("onclick=\"doTimer('/collatz/FrontController?action=getStatistic&cell=" + i + "'," + i + ",400,'" + i + "')\">on</button>");
public class FrontController extends HttpServlet {
    @EJB(name="ejb/CollatzFacade")
    private CollatzFacadeLocal collatzFacade;
...
    public String getCurrentNumber() {
        return collatzFacade.getCurrentNumber().toString();
@Stateless(mappedName = "ejb/CollatzFacade")
public class CollatzFacade implements CollatzFacadeRemote, CollatzFacadeLocal {
    @PersistenceContext(unitName = "CollatzGF-ejbPU", type=PersistenceContextType.TRANSACTION)
    private EntityManager entityManager;
 
    public BigInteger getCurrentNumber() {
        return getComputeGrid().getNextNumberToSearch();
@Entity
@TypeConverters({@TypeConverter(name="BigIntegerToString",dataType=String.class,objectType=BigInteger.class)})
public class ComputeGrid implements Serializable {
    public BigInteger getNextNumberToSearch() {        return nextNumberToSearch;    }
  • Since the current client is only read only and is updated every 200ms by having a @Servlet contact a @EJB that has access to a JTA @PersistenceContext - I don't need to be reading from the database on every request. Fortunately we are using EclipseLink as the JPA 2.0 provider - so we are usually reading from the in-memory L1 cache between database writes from the server. It may be better for us to use a Map based in-memory object that is managed by JPA to avoid the cache hits.

DI 12: JAX-RS 1.1 Client

References DI 12

DI 13: Multithreaded Client

See SVN Rev # 9382 patch https://bugs.eclipse.org/bugs/attachment.cgi?id=195673&action=diff

    protected void threadSafetyPrivate(int numberOfThreads) {
        List<Thread> threadList = new ArrayList<Thread>();
        for(int i=0; i<numberOfThreads; i++) {
            Thread aThread = new Thread(new CollatzRunnable(i));
            threadList.add(aThread);
            // stagger the threads so they are not in lockstep
            try {
            	Thread.sleep(3000);
            } catch (Exception e) {           }
            aThread.start();
        }
 
        // Wait for [threadNumber] threads to complete before ending 
        for(Thread aThread : threadList) {
            try {
                synchronized (aThread) {
                	aThread.join();
                }
            } catch (InterruptedException ie_Ignored) {
            	ie_Ignored.printStackTrace();
            } // The InterruptedException can be ignored 
        }
    }
 
    // Inner class implements Runnable instead of extending Thread directly
    class CollatzRunnable implements Runnable {
    	protected int id;
 
    	public CollatzRunnable(int anId) {
    		id = anId;
    	}
 
        public void run() {
        	//connect(id);
            // We loop an arbitrary number of iterations inside each thread
            processUnitOfWork(id);
        }
    }
 
    public void processUnitOfWork(int threadID) {
        // ask for a work packet
        UnitOfWork uow = null;
        CollatzFacadeRemote collatzFacade = null;
        StringBuffer aBuffer = new StringBuffer();
        String threadName;
        // Endlessly generate RMI requests
        for(;;) {
            try {
            	// Send messages to entire grid in parallel if we connect to more than one server
            	// TODO: create Threads for each remoteObject
            	for(String remoteServer : remoteObjects.get(threadID).keySet()) {
            		threadName = serverDNS[serverInUse] + threadID;
            		collatzFacade = remoteObjects.get(threadID).get(remoteServer);
         			try {
           				// Issue: One JVM halt will affect the entire distributed app.
           				// don't let a node failure halt the host
           				// this remote call can throw an EJBException wrapping a java.rmi.ConnectException                            
           				uow = collatzFacade.requestUnitOfWork(threadName,availableProcessors.get(remoteServer)); 
           				if(null == uow) {
           					// possible redeploy
           					System.out.println("_collatz: " + System.currentTimeMillis() + ": persistence not functioning on server " + serverDNS[serverInUse]);
           				}
           			} catch (Exception e) {//(EJBException e) {
           				//  weblogic.transaction.internal.TimedOutException: Transaction timed out after 29 seconds
           				// or SQLException on constraint violation
           				// EJBException wrapping a java.rmi.ConnectException if the server is not running
           				e.printStackTrace();
           				// mark the current node as down, clear the flag in 5 min
           				//nodeUnavailable.put(remoteServer, true);
           			}
            		// compute collatz for the sequence
            		uow.processInterval();
            		Thread.yield(); // 
            		// return the results to the server
            		// don't cache the remote bean (it may be GC'd or redeployed)
            		collatzFacade = lookupRemoteBean(remoteServer, threadID);
            		boolean retry = true;
            		while(retry) {
            			try {
            				collatzFacade.postUnitOfWork(uow,retry); // possible EJBException or OptimisticLockException
            				retry = false;
            			} catch (Exception ole) {//OptimisticLockException ole) {
            				//System.out.println(ole.getMessage());
            				retry = true;
            				Thread.sleep(1000);
            			}
            		}
            	}
            } catch (Exception e) {
            	e.printStackTrace();
            	try {
            		Thread.sleep(10000);
            	} catch (Exception ex) { }
            }
        }
    }

DI 14: 20110518: Optimize JPQL Queries

  • Some of the queries, especially the ones that are used for web client display in the JSF 2.0 Facelet:ManagedBean combination index.xhtml:MonitorManagedBean.java are naive and not optimized at all. When we start to get a list of thousands of UnitOfWork entities just to be able to get a count for example - we encounter the default 30 seconds database transaction timeout.
CollatzFacade.java
    public long getWorkUnits() {
        Query query = entityManager.createQuery("select object(p) from UnitOfWork p'");
...
  • The quick solution would be to use an aggregate JPQL query instead.
    	Query query = entityManager.createQuery("select COUNT(p) from UnitOfWork p'");
        try {
            workUnits = (Long)query.getSingleResult();
...
  • Or a JPA 2.0 CriteriaQuery
 

DI 110: Volumetrics

  • We need to track calculation iterations to be able to report on a sort of scalar MIPS

DI 111: Analytics

DI 112: Reporting

DI 113: Management

DI 201: Refactor as Framework

Issue 201

  • As is normal computer science behavior - I am thinking of rewriting the distributed collatz application as a more generic framework. This way I can distribute differ types of UnitOfWork as an interface for Mandelbrot fractal generation for example. As we all know there are already frameworks out there such as Apache Hadoop which is an implementation of MapReduce. So I did a quick search first (as usually I search at the end of a project) with the terms Fractal+MapReduce. I was a bit shocked at what I found on the 5th link from the technoticles article on Googe, Hadoop and CouchDB.
  • It looks like I was incorrect. MapReduce is a patented framework from Google now. I know a bit about the history of the framework as a way to reduce the overhead of each development team writing their own distribution and merging of work units. but I did not realize that I might be infringing on a patent by unknowingly writing distributed applications that package and merge pieces of a parallel problem. I modelled the collatz distribute application more on Seti@Home except that the collatz problem really does not have a end - as it never finishes - it just keeps checking packets to infinity or when the electricity goes out.
  • Therefore in the spirit of disclosure - I am stating that I am not looking at anything related to the patent - I am only following the architecture of the original computer science details on Map + Reduce
  • Normally I stay away from anything to do with patents - I have never been directly personally affected by anything patent related in my work - but this patent 7,650,331 awarded for "system and method for efficient large-scale data processing" - really scares me. How are we supposed to develop software that breaks up a parallel problem into concurrent pieces without infringing on this patent awarded in Jan 2011?
  • http://arstechnica.com/open-source/news/2010/01/googles-mapreduce-patent-what-does-it-mean-for-hadoop.ars
  • However, after reading a bit more, it looks like it may just be a defensive patent and not enforced as Google itself uses Apache Hadoop.

Analysis

  • Thats overwith, lets discuss the generic distributed framework.

DI 202: Introduce UOW Queues for new, working , complete

  • We need a way to manage the UnitOfWork packets from the server in the context of ready for computation, in queue (working), complete and timed out.
  • This distributed design needs to be more robust and handle packets that were abandoned due to a node shutdown, client exception or server exception such as an Optimistic Lock Exception.

Implementation

JPA Model

  • This model is shared among the EJB container, the SE and WAR clients

UnitOfWork

@Entity
@TypeConverter(name="BigIntegerToString",dataType=String.class,objectType=BigInteger.class)
public class UnitOfWork implements Serializable {
    private static final long serialVersionUID = 3287861579472326552L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @OneToMany(mappedBy="unitOfWork", fetch=FetchType.EAGER,cascade=CascadeType.ALL)
    // record are an ordered list
    private List<CollatzRecord> records;
 
    @OneToOne(cascade=CascadeType.ALL)
    private ActiveProcessor processor;
    @Column(nullable=false, length=1024)
    @Convert("BigIntegerToString")
    private BigInteger initial;
    @Column(nullable=false, length=1024)
    @Convert("BigIntegerToString")
    private BigInteger extent;
    @OneToOne
    private CollatzRecord knownMax;
    @OneToOne
    private CollatzRecord knownPath;
    private Long startTimestamp;
    private Long endTimestamp;
    @Column(nullable=false) // nullable to avoid /0 error
    private long operations;
 
    private Integer retries;
    // Note: These two may be the same record
    @OneToOne // unidirectional OneToOne
    private CollatzRecord maxPathRecord;
    @OneToOne // unidirectional OneToOne    
    private CollatzRecord maxValueRecord;
    @Version
    private Long version;
 
    // Cached values - do not persist
    @Transient
    private BigInteger interval;

ActiveProcessor

@Entity
public class ActiveProcessor extends DistributedProcessor implements Serializable {
    private static final long serialVersionUID = 6472979075266547411L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @OneToOne
    private UnitOfWork activeUnitOfWork;
    private int category;
    @Version
    private Long version;

CollatzRecord

@Entity
@TypeConverter(name="BigIntegerToString",dataType=String.class,objectType=BigInteger.class)
public class CollatzRecord implements Serializable {
    private static final long serialVersionUID = 4023830926240714638L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @ManyToOne(fetch=FetchType.EAGER)
    private UnitOfWork unitOfWork;
    @Column(nullable=false, length=1024)
    @Convert("BigIntegerToString")
    private BigInteger initial;
    @Column(nullable=false, length=1024)
    @Convert("BigIntegerToString")
    private BigInteger pathLength;
    @Column(nullable=false, length=1024)
    @Convert("BigIntegerToString")
    private BigInteger maximum;
    /** A value of true means this is a maximum value record (independent of max path) */
    @Basic
    private boolean isMaxRecord;
    /** A value of true means this is a maximum path record (independent of max value) */
    @Basic
    private boolean isPathRecord;
 
    @Version
    private Long version;

Parameters

  • This entity represents the persistent shared memory state of the distributed application.
@Entity
@TypeConverters({@TypeConverter(name="BigIntegerToString",dataType=String.class,objectType=BigInteger.class)})
public class Parameters implements Serializable {
    private static final long serialVersionUID = -1979843739878183696L;
 
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
 
    @Column(length=1024)
    @Convert("BigIntegerToString")
    private BigInteger nextNumberToSearch;
    private Long globalStartTimestamp;
    @Column(name="globalduration", length=1024)
    @Convert("BigIntegerToString")
    private BigInteger globalDuration;
    @Column(name="bestIterationsPerSecond", length=1024)
    @Convert("BigIntegerToString")
    private BigInteger bestIterationsPerSecond;
    @Column(name="partitionLength", length=1024)
    @Convert("BigIntegerToString")
    private BigInteger partitionLength;
    // Note: These two may be the same record
    @OneToOne // unidirectional OneToOne
    private CollatzRecord maxPathRecord;
    @OneToOne // unidirectional OneToOne    
    private CollatzRecord maxValueRecord;
 
    @Version
    private Long version;

EE Server

Stateless Session Bean CollatzFacade

CollatzFacade Implementation

@Stateless(mappedName = "ejb/CollatzFacade")
public class CollatzFacade implements CollatzFacadeRemote, CollatzFacadeLocal {
 
    @PersistenceContext(unitName = "CollatzGF-ejbPU", type=PersistenceContextType.TRANSACTION)
    private EntityManager entityManager;
 
    // Some special numbers
    /** The following Collatz number at 61 bits has a maximum path of 64,024667,322193,133530,165877,294264,738020 at 125 bits */
    public static final BigInteger COLLATZ88 = BigInteger.valueOf(1980976057694848447L);     
 
    // Hibernate supports 19bit numbers on Derby by default
    // EclipseLink supports 32bit numbers on Derby by default
    // Override both by declaring a @TypeConverter
    public static final long INITIAL_SEARCH_INTERVAL = 1 << 24;//22; // 20 will generate OLE for 4 threads
    //public static final BigInteger INITIAL_START = BigInteger.ONE.shiftLeft(124);
    public static final BigInteger INITIAL_START = BigInteger.valueOf(27);   
 
    /**
     * 
     */
    public UnitOfWork requestUnitOfWork(String identifier, int threads) {
        // ask collatz for the next unit of work range
        UnitOfWork uow = null;
        ActiveProcessor processor = null;
        try {
            Query queryProcessor = entityManager.createQuery("select object(p) from ActiveProcessor p where p.identifier= :processor'");
            queryProcessor.setParameter("processor", identifier);
            try {
                processor = (ActiveProcessor)queryProcessor.getSingleResult();
            } catch (NoResultException nre) {
                System.out.println("_collatz: " + System.currentTimeMillis() + ": server was redeployed mid-session, or new processor is registering: " + identifier);
            }
 
            // If the processor record does not yet exist - create one            
            if(null == processor) {
                processor = new ActiveProcessor();
                processor.setIdentifier(identifier);
                processor.setThreads(threads);
                entityManager.persist(processor);
                System.out.println("_collatz: " + System.currentTimeMillis() + ": Creating new: " + processor + " for " + identifier);
            }
 
            // ask for the next number to search
            ComputeGrid computeGrid = getComputeGrid();
 
            // update INITIAL_START initial start if it changed - by deleting the record (to protect the database)
            // update search interval if it changed
            if(INITIAL_SEARCH_INTERVAL != computeGrid.getPartitionLength().longValue()) {
                computeGrid.setPartitionLength(BigInteger.valueOf(INITIAL_SEARCH_INTERVAL));
            }
            BigInteger nextNumber = computeGrid.getNextNumberToSearch();
            BigInteger partition = computeGrid.getPartitionLength();
            BigInteger extent = nextNumber.add(partition);
            System.out.println("_collatz: " + System.currentTimeMillis() + ": requestUnitOfWork(" + nextNumber + "-" + extent + ") for processor " + processor);
            // create a unit of work packet for the client
            uow = new UnitOfWork();
            // make the number odd
            if(!nextNumber.testBit(0)) {
                uow.setInitial(nextNumber.add(BigInteger.ONE));
            } else {
            	uow.setInitial(nextNumber);
            }
            uow.setExtent(extent);
            // TODO: max will be different for different ranges
            // TODO: KnownMax/Path are kind of redundant now that we associate a record instead of just a BigInteger with UOW and ComputeGrid
            uow.setKnownMax(computeGrid.getMaxValueRecord());
            uow.setKnownPath(computeGrid.getMaxPathRecord());
            uow.setMaxPathRecord(computeGrid.getMaxPathRecord());
            uow.setMaxValueRecord(computeGrid.getMaxValueRecord());
            uow.setProcessor(processor);
            uow.setStartTimestamp((new Date()).getTime());
            uow.setRetries(0);
 
            // only update the database with the new global numbers if the packet was returned
            //computeGrid.setNextNumberToSearch(extent);
            //em.persist(computeGrid)
            entityManager.persist(processor);
            entityManager.persist(uow);
        //} catch (OptimisticLockException ole) { // should not get this for different processors
        //    ole.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return uow;
    }
 
 
    // TODO: if packet not received - remark uow for another processor
    /**
     * Handles OptimisticLockException
     */
    // split storage from computeGrid, on ole just read
    public void postUnitOfWork(UnitOfWork uow, boolean retry) {
        // get the current maximum and path just under the initial # of this uow
        ComputeGrid computeGrid = null;
            // The first time through we want to read and compare (the same as we would if we encountered an OptimisticLockException)
            boolean hasOptimisticLockException = true;
            while (hasOptimisticLockException) {
                hasOptimisticLockException = false;
                try {
                    computeGrid = getComputeGrid();
                    // collate local and global records
                if(computeGrid.getMaxPath().compareTo(uow.getMaxPath()) < 0) {
                	StringBuffer aBuffer = new StringBuffer("_collatz: ");
                	aBuffer.append(System.currentTimeMillis());
                	aBuffer.append(": New max path : ");
                	aBuffer.append(uow.getMaxPathRecord().getInitial());
                	aBuffer.append(",");
                	aBuffer.append(uow.getMaxPathRecord().getPathLength());
                	aBuffer.append(",");
                	aBuffer.append(uow.getMaxPathRecord().getMaximum());
                	aBuffer.append(" via: ");
                	aBuffer.append(uow.getProcessor().getIdentifier());
                	aBuffer.append(" # ");
                	aBuffer.append(uow.getId());
                	aBuffer.append(" @ " );
            		aBuffer.append(uow.getMIPS());
            		aBuffer.append(" MIPS");
            		computeGrid.setMaxPathRecord(uow.getMaxPathRecord());
                }
                if(computeGrid.getMaxValue().compareTo(uow.getMaxValue()) < 0) {
                	StringBuffer aBuffer = new StringBuffer("_collatz: ");
                	aBuffer.append(System.currentTimeMillis());
                	aBuffer.append(": New max value: ");
                	aBuffer.append(uow.getMaxValueRecord().getInitial());
                	aBuffer.append(",");
                	aBuffer.append(uow.getMaxValueRecord().getPathLength());
                	aBuffer.append(",");
                	aBuffer.append(uow.getMaxValueRecord().getMaximum());
                	aBuffer.append(" via: ");
                	aBuffer.append(uow.getProcessor().getIdentifier());
                	aBuffer.append(" # ");
                	aBuffer.append(uow.getId());
                	aBuffer.append(" @ " );
            		aBuffer.append(uow.getMIPS());
            		aBuffer.append(" MIPS");
            		computeGrid.setMaxValueRecord(uow.getMaxValueRecord());
                }
                BigInteger nextNumber = computeGrid.getNextNumberToSearch();
                BigInteger partition = computeGrid.getPartitionLength();
                BigInteger extent = nextNumber.add(partition);
                // only update the database with the new global numbers if the packet was returned
                computeGrid.setNextNumberToSearch(extent);  // no persist req for cm transactions/
                // TODO: SET global duration so mips does not drop when the server is idle
 
                computeGrid.setOperations(BigInteger.valueOf(uow.getOperations()).add(computeGrid.getOperations()));
                // Perf: the database record may have been modified since it was last read.
            // filter the new record from the run
            List<CollatzRecord> records = uow.getRecords();
            if(null != records && records.size() > 0) {
                //System.out.println("_collatz: " + System.currentTimeMillis() + ": persisting " + records.size() + " CollatzRecords");
                for(CollatzRecord record : records) {
                    // need to persist max/path as well
                    entityManager.persist(record);
                }
            }
            } catch (OptimisticLockException ole ) {
            	System.out.println(ole.getMessage());
            	hasOptimisticLockException = false;
            }
        }
    }
 
    public ActiveProcessor registerProcessor(String identifier, int threads) {
        ActiveProcessor processor = null;
        // check if processor already registered
        return processor;
    }
 
    public UnitOfWork getUnitOfWork(Long id) {
        return entityManager.find(UnitOfWork.class, id);
    }
 
    // Management API
    public BigInteger getCurrentNumber() {
        return getComputeGrid().getNextNumberToSearch();
    }
 
    public String getCurrentNumberDelimited() {
        return getDelimitedNumber(getCurrentNumber().toString());
    }
 
    public void setCurrentNumber(BigInteger number) {
        ComputeGrid computeGrid = getComputeGrid();
        computeGrid.setNextNumberToSearch(number);
        // persist this
        entityManager.persist(computeGrid);
    }
 
    public String getPartitionLengthDelimited() {
        return getDelimitedNumber(getPartitionLength().toString());
    }
 
    public BigInteger getPartitionLength() {
        return getComputeGrid().getPartitionLength();
    }
 
    public void setPartitionLength(BigInteger partition) {
        ComputeGrid computeGrid = getComputeGrid();
        computeGrid.setPartitionLength(partition);
        // persist this
        entityManager.persist(computeGrid);
    }
 
    private synchronized ComputeGrid initializeComputeGrid(UnitOfWork uow) {
        ComputeGrid computeGrid = new ComputeGrid();
        computeGrid.setPartitionLength(BigInteger.valueOf(INITIAL_SEARCH_INTERVAL));
        computeGrid.setGlobalStartTimestamp((new Date()).getTime());
        computeGrid.setNextNumberToSearch(INITIAL_START);
        computeGrid.setOperations(BigInteger.ZERO);
 
        CollatzRecord milestoneRecord = new CollatzRecord();
        milestoneRecord.setIsMaxRecord(true);
        milestoneRecord.setIsPathRecord(true);
        milestoneRecord.setInitial(computeGrid.getNextNumberToSearch());
        milestoneRecord.setMaximum(BigInteger.valueOf(9232));
        milestoneRecord.setPathLength(BigInteger.valueOf(110));
        computeGrid.setMaxPathRecord(milestoneRecord);
        computeGrid.setMaxValueRecord(milestoneRecord);
        System.out.println("_collatz: " + System.currentTimeMillis() + ": First packet: " + computeGrid);
        // UnitOfWork will be persisted later in request - if we are processing a response then persist changes
        try {
            if(null != uow) {
                milestoneRecord.setUnitOfWork(uow);
                uow.setKnownMax(milestoneRecord);
                uow.setKnownPath(milestoneRecord);
                uow.setMaxPathRecord(milestoneRecord);
                uow.setMaxValueRecord(milestoneRecord);
                // TODO: set maximums on ComputeGrid entity
                entityManager.persist(uow);
            }
            entityManager.persist(milestoneRecord);
            synchronized(computeGrid) {
            	entityManager.persist(computeGrid);
            }
            // computeGrid.setPartitionLength(BigInteger.valueOf(INITIAL_SEARCH_INTERVAL));
        } catch (Exception ole) {
            System.out.println("_collatz: " + System.currentTimeMillis() + ": OptimisticLockException for: " + computeGrid);
            // merge changes and re-persist
            ComputeGrid oldComputeGrid = null;
            // JPA 2.0
            //Query query = em.createQuery("select object(c) from ComputeGrid c", ComputeGrid.class);
            // JPA 1.0
            Query query = entityManager.createQuery("select object(p) from ComputeGrid c");
            try {
                oldComputeGrid = (ComputeGrid)query.getSingleResult();
            } catch (NoResultException nre) {
                //System.out.println("_collatz: " + System.currentTimeMillis() + ": server was redeployed mid-session: ComputeGrid is null");
                return null;
            }
            // Merge results
            if(computeGrid.getMaxPath().compareTo(oldComputeGrid.getMaxPath()) > 0) {
                oldComputeGrid.setMaxPathRecord(computeGrid.getMaxPathRecord());
            }
            if(computeGrid.getMaxValue().compareTo(oldComputeGrid.getMaxValue()) > 0) {
                oldComputeGrid.setMaxValueRecord(computeGrid.getMaxValueRecord());
            }
        }
        return computeGrid;
    }
 
    /**
     * This method should never return a null Entity
     * @return
     */
    private synchronized ComputeGrid getComputeGrid() {        
        // We will let the persistence provider L1 cache store the entities between requests in the same session bean session. (no instance variable on this bean)
        ComputeGrid computeGrid = null;
        // JPQL Ref: http://download.oracle.com/docs/cd/E11035_01/kodo41/full/html/ejb3_langref.html
        // JPA 2.0
        //Query query = em.createQuery("select object(c) from ComputeGrid c", ComputeGrid.class);
        // JPA 1.0
        Query query = entityManager.createQuery("select object(c) from ComputeGrid c");
        try {
            computeGrid = (ComputeGrid)query.getSingleResult();
        } catch (NoResultException nre) {
            //System.out.println("_collatz: " + System.currentTimeMillis() + ": server was redeployed mid-session: computeGrid is null");
            computeGrid = initializeComputeGrid(null);
        }
        return computeGrid;
    }
 
    public String getMipsDelimited() {
        return getDelimitedNumber(String.valueOf(getMips()));
    }
 
    // JSF Integration
    public int getMips() {
    	BigInteger operations = BigInteger.ZERO;
    	int mips = 0;
    	Query query = entityManager.createQuery("select object(c) from ComputeGrid c");
    	ComputeGrid computeGrid = null;
        try {
            computeGrid = (ComputeGrid)query.getSingleResult();
            operations = computeGrid.getOperations();
            long startTime = computeGrid.getGlobalStartTimestamp();
            long duration =  System.currentTimeMillis() - startTime;
            if(duration > 0) {
                mips = (operations.multiply(BigInteger.valueOf(1000L))).divide(BigInteger.valueOf(duration)).intValue();
            }
        } catch (NonUniqueResultException nure) {
            System.out.println("_collatz: " + System.currentTimeMillis() + ": server was redeployed mid-session, or new processor is registering: unable to compute MIPS:" + nure.getMessage());
            mips = -1;            
        } catch (NoResultException nre) {
            System.out.println("_collatz: " + System.currentTimeMillis() + ": server was redeployed mid-session, or new processor is registering: ");
        }
        return mips;
    }
 
    public long getWorkUnits() {
    	long workUnits = -1;
    	Query query = entityManager.createQuery("select COUNT(p) from UnitOfWork p'");
        try {
            workUnits = (Long)query.getSingleResult();
        } catch (NoResultException nre) {
            System.out.println("_collatz: " + System.currentTimeMillis() + ": server was redeployed mid-session, or new processor is registering: ");
        }
        return workUnits;
    }
 
    public String getMaxPathDelimited() {
        return getDelimitedNumber(getMaxPath().toString());
    }    
 
    public BigInteger getMaxPath() {
    	BigInteger maxPath = BigInteger.ZERO;
    	Query query = entityManager.createQuery("select object(c) from ComputeGrid c");
    	ComputeGrid computeGrid = null;
        try {
            computeGrid = (ComputeGrid)query.getSingleResult();
            maxPath = computeGrid.getMaxPath();
        } catch (NoResultException nre) {
            System.out.println("_collatz: " + System.currentTimeMillis() + ": server was redeployed mid-session, or new processor is registering: ");
        }
        return maxPath;
    }
 
    public String getMaxValueDelimited() {
        return getDelimitedNumber(getMaxValue().toString());
    }
 
    /**
     * Return a comma delimited number 9323 = 9,232
     * @return
     */
    private String getDelimitedNumber(String number) {        
        // insert commas from the end every 6 digits
        StringBuffer buffer = new StringBuffer();
        short radix = 2;
        boolean skipFirst = true;
        for(int i=number.length();i > 0; i--) {
            if(radix++ > 1) {
                if(!skipFirst) {
                    buffer.append(",");
                } else {
                    skipFirst = false;
                }
                radix = 0;                
            }
            buffer.append(number.charAt(i-1));
        }
        return buffer.reverse().toString();
    }
 
    public BigInteger getMaxValue() {
    	BigInteger maxValue = BigInteger.ZERO;
    	Query query = entityManager.createQuery("select object(c) from ComputeGrid c");
    	ComputeGrid computeGrid = null;
        try {
            computeGrid = (ComputeGrid)query.getSingleResult();
            maxValue = computeGrid.getMaxValue();
        } catch (NoResultException nre) {
            System.out.println("_collatz: " + System.currentTimeMillis() + ": server was redeployed mid-session, or new processor is registering: ");
        }
        return maxValue;    	
    }
 
    public int getNumberProcessors() {
    	int numProcessors = -1;
        Query queryProcessor = entityManager.createQuery("select object(p) from ActiveProcessor p'");
        List processors = null;
        try {
            processors = queryProcessor.getResultList();
            if(null != processors) {
            	numProcessors = processors.size();
            }
        } catch (NoResultException nre) {
            System.out.println("_collatz: " + System.currentTimeMillis() + ": server was redeployed mid-session, or new processor is registering: ");
        }
        return numProcessors;
 
    }
 
    /**
     * Default constructor - test private override. 
     */
    //private CollatzFacade() {    } // not valid for pojo spec - for variant testing only
}

CollatzFacadeRemote

@Remote
public interface CollatzFacadeRemote {
    public ActiveProcessor registerProcessor(String identifier, int threads);
    public UnitOfWork getUnitOfWork(Long id);
    public void postUnitOfWork(UnitOfWork uow, boolean retry);
    public UnitOfWork requestUnitOfWork(String identifier, int threads);
    // Management API
    public String getCurrentNumberDelimited();    
    public BigInteger getCurrentNumber();
    public void setCurrentNumber(BigInteger number);
    public String getPartitionLengthDelimited();
    public BigInteger getPartitionLength();
    public void setPartitionLength(BigInteger partition);
    // JSF Integration
    public int getMips();
    public String getMipsDelimited();
 
    public long getWorkUnits();
    public String getMaxPathDelimited();
    public BigInteger getMaxPath();
    public String getMaxValueDelimited();
    public BigInteger getMaxValue();
    public int getNumberProcessors();
}

persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="CollatzGF-ejbPU" transaction-type="JTA">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <!--provider>org.hibernate.ejb.HibernatePersistence</provider-->
    <!-- jta-data-source>jdbc/Collatz2</jta-data-source--> <!-- glassfish -->
    <jta-data-source>collatzRemote</jta-data-source> <!-- weblogic -->
    <!-- mapping-file>META-INF/orm.xml</mapping-file-->
    <class>org.dataparallel.collatz.business.ActiveProcessor</class>
    <class>org.dataparallel.collatz.business.CollatzRecord</class>
    <class>org.dataparallel.collatz.business.Parameters</class>
    <class>org.dataparallel.collatz.business.UnitOfWork</class>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <!-- shared-cache-mode>NONE</shared-cache-mode-->
    <properties>
      <!-- EclipseLink -->
      <property name="eclipselink.target-server" value="SunAS9"/>
      <property name="eclipselink.target-database" value="Derby"/>
      <!-- turn off DDL generation after the model is stable -->
      <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
      <property name="eclipselink.ddl-generation.output-mode" value="database"/>
      <property name="eclipselink.logging.level" value="FINEST"/>
      <!-- enable SQL parameter binding visibility logging to override ER 329852 -->
      <property name="eclipselink.logging.parameters" value="true"/>
      <!-- new for 10.3.4.0 http://wiki.eclipse.org/EclipseLink/Examples/JPA/Logging#Server_Logging  -->
      <property name="eclipselink.logging.logger" value="DefaultLogger"/>      
 
      <!-- http://netbeans.org/kb/docs/web/hibernate-jpa.html -->
      <property name="hibernate.show_sql" value="true"/>
      <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
    </properties>
  </persistence-unit>
</persistence>

orm.xml

  • Not required - as we use native EclipseLink annotations where required beyond the JPA 2.0 specification
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings version="1.0" xmlns="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd">
    <!-- converter class="jpa.ConverterBigInteger.class" name="BigIntegerToString" /-->
</entity-mappings>

application.xml

<?xml version="1.0" encoding="UTF-8"?>
<application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:application="http://java.sun.com/xml/ns/javaee/application_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/application_5.xsd" id="Application_ID" version="5">
  <display-name>org.obrienlabs.distributed.CollatzEAR</display-name>
  <module>
    <java>org.obrienlabs.distributed.CollatzModel.jar</java>
  </module>
  <module>
    <ejb>org.obrienlabs.distributed.CollatzEJB.jar</ejb>
  </module>
  <module>
    <web>
      <web-uri>org.obrienlabs.distributed.CollatzWeb.war</web-uri>
      <context-root>collatz</context-root>
    </web>
  </module>
  <library-directory>lib</library-directory>
</application>

weblogic-application.xml

  • WebLogic specific elements not required
<?xml version="1.0" encoding="UTF-8"?>
<wls:weblogic-application xmlns:wls="http://xmlns.oracle.com/weblogic/weblogic-application" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/javaee_5.xsd http://xmlns.oracle.com/weblogic/weblogic-application http://xmlns.oracle.com/weblogic/weblogic-application/1.2/weblogic-application.xsd">
    <!--weblogic-version:10.3.4-->
    <wls:application-param>
        <wls:param-name>webapp.encoding.default</wls:param-name>
        <wls:param-value>UTF-8</wls:param-value>
    </wls:application-param>
</wls:weblogic-application>

EJB project manifest override

Manifest-Version: 1.0 Class-Path: org.obrienlabs.distributed.CollatzModel.jar

weblogic-ejb-jar.xml

<?xml version="1.0" encoding="UTF-8"?>
<wls:weblogic-ejb-jar xmlns:wls="http://xmlns.oracle.com/weblogic/weblogic-ejb-jar" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd http://xmlns.oracle.com/weblogic/weblogic-ejb-jar http://xmlns.oracle.com/weblogic/weblogic-ejb-jar/1.2/weblogic-ejb-jar.xsd">
    <!--weblogic-version:10.3.4-->
</wls:weblogic-ejb-jar>

createDDL.jdbc

  • This DDL is generated by EclipseLink for Derby XA
CREATE TABLE ACTIVEPROCESSOR (ID BIGINT NOT NULL, CATEGORY INTEGER, CORES INTEGER, IDENT VARCHAR(255), PERFORMANCE INTEGER, RANK INTEGER, THREADS INTEGER, VERSION BIGINT, ACTIVEUNITOFWORK_ID BIGINT, PRIMARY KEY (ID))
CREATE TABLE COLLATZRECORD (ID BIGINT NOT NULL, INITIAL VARCHAR(1024) NOT NULL, ISMAXRECORD SMALLINT DEFAULT 0, ISPATHRECORD SMALLINT DEFAULT 0, MAXIMUM VARCHAR(1024) NOT NULL, PATHLENGTH VARCHAR(1024) NOT NULL, VERSION BIGINT, UNITOFWORK_ID BIGINT, PRIMARY KEY (ID))
CREATE TABLE PARAMETERS (ID BIGINT NOT NULL, bestIterationsPerSecond VARCHAR(1024), globalduration VARCHAR(1024), GLOBALSTARTTIMESTAMP BIGINT, NEXTNUMBERTOSEARCH VARCHAR(1024), partitionLength VARCHAR(1024), VERSION BIGINT, MAXPATHRECORD_ID BIGINT, MAXVALUERECORD_ID BIGINT, PRIMARY KEY (ID))
CREATE TABLE UNITOFWORK (ID BIGINT NOT NULL, ENDTIMESTAMP BIGINT, EXTENT VARCHAR(1024) NOT NULL, INITIAL VARCHAR(1024) NOT NULL, OPERATIONS BIGINT NOT NULL, RETRIES INTEGER, STARTTIMESTAMP BIGINT, VERSION BIGINT, KNOWNMAX_ID BIGINT, KNOWNPATH_ID BIGINT, MAXPATHRECORD_ID BIGINT, MAXVALUERECORD_ID BIGINT, PROCESSOR_ID BIGINT, PRIMARY KEY (ID))
ALTER TABLE ACTIVEPROCESSOR ADD CONSTRAINT CTVPRCSSCTVNTFWRKD FOREIGN KEY (ACTIVEUNITOFWORK_ID) REFERENCES UNITOFWORK (ID)
ALTER TABLE COLLATZRECORD ADD CONSTRAINT CLLTZRCORDNTFWRKID FOREIGN KEY (UNITOFWORK_ID) REFERENCES UNITOFWORK (ID)
ALTER TABLE PARAMETERS ADD CONSTRAINT PRMTRSMXPTHRCORDID FOREIGN KEY (MAXPATHRECORD_ID) REFERENCES COLLATZRECORD (ID)
ALTER TABLE PARAMETERS ADD CONSTRAINT PRMTRSMXVLRECORDID FOREIGN KEY (MAXVALUERECORD_ID) REFERENCES COLLATZRECORD (ID)
ALTER TABLE UNITOFWORK ADD CONSTRAINT NTOFWORKPRCESSORID FOREIGN KEY (PROCESSOR_ID) REFERENCES ACTIVEPROCESSOR (ID)
ALTER TABLE UNITOFWORK ADD CONSTRAINT NTOFWORKKNWNPATHID FOREIGN KEY (KNOWNPATH_ID) REFERENCES COLLATZRECORD (ID)
ALTER TABLE UNITOFWORK ADD CONSTRAINT NTFWRKMXPTHRCORDID FOREIGN KEY (MAXPATHRECORD_ID) REFERENCES COLLATZRECORD (ID)
ALTER TABLE UNITOFWORK ADD CONSTRAINT NTOFWORKKNOWNMAXID FOREIGN KEY (KNOWNMAX_ID) REFERENCES COLLATZRECORD (ID)
ALTER TABLE UNITOFWORK ADD CONSTRAINT NTFWRKMXVLRECORDID FOREIGN KEY (MAXVALUERECORD_ID) REFERENCES COLLATZRECORD (ID)
CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL, SEQ_COUNT DECIMAL(15), PRIMARY KEY (SEQ_NAME))
INSERT INTO SEQUENCE(SEQ_NAME, SEQ_COUNT) values ('SEQ_GEN', 0)

dropDDL.jdbc

  • This DDL is generated by EclipseLink for Derby XA
ALTER TABLE ACTIVEPROCESSOR DROP CONSTRAINT CTVPRCSSCTVNTFWRKD
ALTER TABLE COLLATZRECORD DROP CONSTRAINT CLLTZRCORDNTFWRKID
ALTER TABLE PARAMETERS DROP CONSTRAINT PRMTRSMXPTHRCORDID
ALTER TABLE PARAMETERS DROP CONSTRAINT PRMTRSMXVLRECORDID
ALTER TABLE UNITOFWORK DROP CONSTRAINT NTOFWORKPRCESSORID
ALTER TABLE UNITOFWORK DROP CONSTRAINT NTOFWORKKNWNPATHID
ALTER TABLE UNITOFWORK DROP CONSTRAINT NTFWRKMXPTHRCORDID
ALTER TABLE UNITOFWORK DROP CONSTRAINT NTOFWORKKNOWNMAXID
ALTER TABLE UNITOFWORK DROP CONSTRAINT NTFWRKMXVLRECORDID
DROP TABLE ACTIVEPROCESSOR
DROP TABLE COLLATZRECORD
DROP TABLE PARAMETERS
DROP TABLE UNITOFWORK
DELETE FROM SEQUENCE WHERE SEQ_NAME = 'SEQ_GEN'

AJAX Integration

  • FrontController servlet pattern - primarily used as the XML backend to an Asynchronous Javascript and XML thicker client.
  • At the time of this writing WebLogic did not implement the Servlet 3.0 API yet - so no @Servlet annotation.
public class FrontController extends HttpServlet {
    private static final long serialVersionUID = -312633509671504746L;
 
    @EJB(name="ejb/CollatzFacade")
    private CollatzFacadeLocal collatzFacade;
 
    public static final String EMPTY_STRING = "";
    public static final String FRONT_CONTROLLER_ACTION = "action";
    public static final String FRONT_CONTROLLER_ACTION_DEMO = "demo";
    public static final String FRONT_CONTROLLER_ACTION_GET_STATISTIC = "getStatistic";    
 
    /**
     * @see HttpServlet#HttpServlet()
     */
    public FrontController() {
        super();
    }
 
    public String getCurrentNumber() {
        return collatzFacade.getCurrentNumberDelimited();
    }
 
    public String getInterval() {
        return collatzFacade.getPartitionLengthDelimited();
    }
 
    public String getMips() {
        return collatzFacade.getMipsDelimited();
    }
 
    // read only
    public String getWorkUnits() {
        return String.valueOf(collatzFacade.getWorkUnits());
    }
 
    public String getMaxPath() {
        return collatzFacade.getMaxPathDelimited();
    }
 
    public String getMaxValue() {
        return collatzFacade.getMaxValueDelimited();
    }
 
    public String getProcessors() {
        return String.valueOf(collatzFacade.getNumberProcessors());
    }
 
    private void processGetStatistic(HttpServletRequest request, HttpServletResponse response, PrintWriter out) {
        String cell = request.getParameter("cell");
        int cellNumber = -1;
        if(null != cell) {
            cellNumber = Integer.parseInt(cell);
        }
        StringBuffer xmlBuffer = new StringBuffer();
        //long number = System.nanoTime();
        xmlBuffer.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
        xmlBuffer.append("<state>");
        switch (cellNumber) {
            case 0:
                //long random = Math.round(Math.random() * 1000);
                xmlBuffer.append(this.getInterval());
                break;
            case 1:
                xmlBuffer.append(getCurrentNumber());
                break;
            case 2:
                xmlBuffer.append(this.getMaxPath());
                break;
            case 3:
                xmlBuffer.append(this.getMaxValue());
                break;
            case 4:
                xmlBuffer.append(this.getWorkUnits());
                break;
            case 5:
                xmlBuffer.append(this.getMips());
                break;
/*            default:
                xmlBuffer.append(number);
                break;*/
        }
        xmlBuffer.append("</state>");
        out.println(xmlBuffer.toString());        
        //StringBuffer outBuffer = new StringBuffer("Thread: ");        
        //System.out.println("_xml: " + xmlBuffer.toString());
    }
 
    private void processDemoCommand(HttpServletRequest request, HttpServletResponse response, PrintWriter out) {
        String cell = request.getParameter("cell");
        int cellNumber = -1;
        if(null != cell) {
            cellNumber = Integer.parseInt(cell);
        }
 
        StringBuffer xmlBuffer = new StringBuffer();
        long number = System.nanoTime();
        xmlBuffer.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
        xmlBuffer.append("<state>");
        switch (cellNumber) {
        case 0:
            long random = Math.round(Math.random() * 1000);
            xmlBuffer.append(random);
            break;
        case 1:
            xmlBuffer.append(number);
            break;
        default:
            xmlBuffer.append(number);
            break;
        }
        xmlBuffer.append("</state>");
        out.println(xmlBuffer.toString());        
        //StringBuffer outBuffer = new StringBuffer("Thread: ");        
        System.out.println("_xml: " + xmlBuffer.toString());
    }
 
    private void processAction(HttpServletRequest aRequest, HttpServletResponse aResponse) {
        PrintWriter out = null;
        try {
                //HttpSession aSession = aRequest.getSession(true);
                String action = aRequest.getParameter(FRONT_CONTROLLER_ACTION);
                if(null == action) {
                    action = FRONT_CONTROLLER_ACTION_DEMO;
                }
                // Process requests
                if(action.equalsIgnoreCase(FRONT_CONTROLLER_ACTION_DEMO)) {
                    aResponse.setContentType("text/xml");
                    Writer writer = new BufferedWriter(new OutputStreamWriter(aResponse.getOutputStream(),"UTF-8"));
                    out = new PrintWriter(writer, true);
                    processDemoCommand(aRequest, aResponse, out);
                }
                if(action.equalsIgnoreCase(FRONT_CONTROLLER_ACTION_GET_STATISTIC)) {
                    aResponse.setContentType("text/xml");
                    Writer writer = new BufferedWriter(new OutputStreamWriter(aResponse.getOutputStream(),"UTF-8"));
                    out = new PrintWriter(writer, true);
                    processGetStatistic(aRequest, aResponse, out);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
    }
 
	/**
	 * @see Servlet#init(ServletConfig)
	 */
	@Override
    public void init(ServletConfig config) throws ServletException {
	}
 
	/**
	 * @see Servlet#getServletConfig()
	 */
	@Override
    public ServletConfig getServletConfig() {
		return null;
	}
 
	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	@Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        processAction(request, response);
	}
 
	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	@Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        processAction(request, response);
 
	}
 
	/**
	 * @see HttpServlet#doPut(HttpServletRequest, HttpServletResponse)
	 */
	@Override
    protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	}
 
	/**
	 * @see HttpServlet#doDelete(HttpServletRequest, HttpServletResponse)
	 */
	@Override
    protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	}
 
	/**
	 * @see HttpServlet#doHead(HttpServletRequest, HttpServletResponse)
	 */
	@Override
    protected void doHead(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	}
}

AJAX JSP client

  • This AJAX client is an old school JSP file using banned scriptlets - as an AJAX proof of concept.
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
        <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
        <meta http-equiv="Content-Style-Type" content="text/css">
        <meta http-equiv="expires" content="Wed, 26 Feb 1997 08:21:57 GMT">
        <link rel="stylesheet" type="text/css" href="styles.css">        
 
<title>Collatz Java EE Active Client</title>
<% int processingUnits = 6; %>
<script language="JavaScript" type="text/javascript">
 function FactoryXMLHttpRequest() {
	   if(window.XMLHttpRequest) {
		   // Mozilla
		   return new XMLHttpRequest();
	   } else if(window.ActiveXObject) {
		   try {
		    // Internet Explorer only
            return new ActiveXObject("Microsoft.XMLHTTP");
		   } catch (e) {
		   }
       }
       // Verify Chrome, Firefox, Opera and Apple
       throw new Error("Could not get an AJAX XMLHttpRequest Object");
 }
 
 function Ajax() {
	 this._xmlHttp = new FactoryXMLHttpRequest();
 }
 
 // This code is loosely based on 
 // p.22 of Ajax Patterns and Best Practices by Christian Gross (2006)
 // http://books.google.com/books?id=qNzBbUWhGM0C&printsec=frontcover&dq=ajax+patterns+and&cd=2#v=onepage&q&f=false
 // and http://www.w3schools.com/js/js_timing.asp
 // htmlDOMElementId must be an integer
 function AjaxUpdateEvent(htmlDOMelementId, status, statusText, responseText, responseXML) {
	 // This line updates the DOM <span id="[0-9]+"/>
	 document.getElementById(htmlDOMelementId).innerHTML = responseText;
	 // flash data cell only if data has changed
	 var color = "orange";
	 if(flicker[htmlDOMelementId] < 1) {
		 flicker[htmlDOMelementId] = 1;
		 color = "white";
	 } else {
		 flicker[htmlDOMelementId] = 0;
	 }
     if(responseText != lastValue[htmlDOMelementId]) {
         document.getElementById(htmlDOMelementId).style.color = color;
         lastValue[htmlDOMelementId] = responseText;
     }
 
 }
 
 // display flags
 var flicker = new Array();
 var lastValue = new Array();
 // Timer variables
 var timers = new Array();
 var timerStateArray = new Array();
 var ajax = new Array();
 // initialize all arrays
 for (i=0;i<<%=processingUnits%>;i=i+1) {
     timerStateArray[i] = 0;
     ajax[i] = new Ajax();
     ajax[i].complete = AjaxUpdateEvent;
     flicker[i] = 0;
 }
 
 function Ajax_call(url, htmlDOMelementId) {
	 var instance = this;
	 this._xmlHttp.open('GET', url, true);
	 // inner anonymous function
	 this._xmlHttp.onreadystatechange = function() {
		 switch(instance._xmlHttp.readyState) {
		 case 1:
			 instance.loading();
			 break;
		 case 2:
			 instance.loaded();
			 break;
		 case 3:
			 instance.interactive();
			 break;
		 case 4:
			 // pass parameters
			 instance.complete(
					 htmlDOMelementId,
					 instance._xmlHttp.status,
					 instance._xmlHttp.statusText,
					 instance._xmlHttp.responseText,
					 instance._xmlHttp.responseXML);
			 break;
		 }
	 };
	 this._xmlHttp.send(null);
 }
 
 function Ajax_loading(){ }
 function Ajax_loaded(){ }
 function Ajax_interactive(){ }
 function Ajax_complete(htmlDOMelementId, status, statusText, responseText, responseHTML){ }
 
 // create static class functions
 Ajax.prototype.loading = Ajax_loading;
 Ajax.prototype.loaded = Ajax_loaded;
 Ajax.prototype.interactive = Ajax_interactive;
 Ajax.prototype.complete = Ajax_complete;
 Ajax.prototype.call = Ajax_call;
 
 // Base case: Switch the timer flag and call the main loop
 function doTimer(url,cell,speed,htmlDOMelementId) {
     if(!timerStateArray[cell]) {
    	  timerStateArray[cell] = 1;
          timedCall(url,cell,speed,htmlDOMelementId);
     }
 }
 // Main timing loop calls itself
 function timedCall(url,cell,speed,htmlDOMelementId) {
     ajax[cell].call(url,htmlDOMelementId);
     if(timerStateArray[cell]) {
           timers[cell] = setTimeout(function() { timedCall(url,cell,speed,htmlDOMelementId); }, speed); // no speed = max speed
     }
 }
 
 
</script>
</head>
<body text="#ffffff" bgcolor="#303030" link="#33D033" vlink="#D030D0" alink="#D03000">
<!-- @&rnd=<%=String.valueOf(Math.random())%>')"-->
 <table border="0">
        <%
        for(int i=0;i<processingUnits;i++) {
        out.println("<tr bgcolor=\"#2d1d4f\">");
        out.println("<td><span id=\"" + i + "\" class=\"refdesc-d\">" + i + "</span>");
        out.println("</td>");
        out.println("<td><span id=\"b" + i + "\">");
        out.println(" <button ");
        out.println("onMouseOver=\"ajax[" + i + "].call('/collatz/FrontController?action=getStatistic&cell=" + i + "','" + i + "')\"  ");
        out.println("onclick=\"doTimer('/collatz/FrontController?action=getStatistic&cell=" + i + "'," + i + ",400,'" + i + "')\">on</button>");
        out.println(" <button ");
        out.println("onclick=\"timerStateArray[" + i + "]=0\">off</button>");
        out.println("</span>");
        out.println("</td>");
        out.println("</tr>");
        }        
        %>
 </table>
 <br/><br/><br/><br/><br/>
 <p class="ref2">Make sure that "Tools | Internet Options | Browsing History - is set to "Every time I visit the webpage" instead of the default "Automatically" so the XMLHttpRequest call will reach the server on subsequent calls.</p>
</body>
</html>

JSF 2.0 Integration

Managed Beans

@ManagedBean(name="monitorBean")
@SessionScoped
public class MonitorManagedBean {
    @EJB(name="ejb/CollatzFacade")
    private CollatzFacadeLocal collatzFacade;
 
    private String currentNumber;
    private String interval;
    private String mips;
    private String workUnits;
    private String maxPath;
    private String maxValue;
    private String processors;
 
    public String getCurrentNumber() {
    	currentNumber = collatzFacade.getCurrentNumber().toString();
		return currentNumber;
	}
 
	public void setCurrentNumber(String currentNumber) {
		this.currentNumber = currentNumber;
		collatzFacade.setCurrentNumber(BigInteger.valueOf(Long.valueOf(currentNumber).longValue()));
	}
 
	public String getInterval() {
		interval = collatzFacade.getPartitionLength().toString();
		return interval;
	}
 
	public void setInterval(String interval) {
		this.interval = interval;
	}
 
	public String getMips() {
		mips = String.valueOf(collatzFacade.getMips());
		return mips;
	}
 
	// Mips is read only
	public void setMips(String mips) {
		this.mips = mips;
	}
 
	// read only
	public String getWorkUnits() {
		workUnits = String.valueOf(collatzFacade.getWorkUnits());
		return workUnits;
	}
 
	public void setWorkUnits(String workUnits) {
		this.workUnits = workUnits;
	}
 
	public String getMaxPath() {
		maxPath = collatzFacade.getMaxPath().toString();
		return maxPath;
	}
 
	// could be read only
	public void setMaxPath(String maxPath) {
		this.maxPath = maxPath;
	}
 
	public String getMaxValue() {
		maxValue = collatzFacade.getMaxValue().toString();
		return maxValue;
	}
 
	// could be read only
	public void setMaxValue(String maxValue) {
		this.maxValue = maxValue;
	}
 
	public String getProcessors() {
		processors = String.valueOf(collatzFacade.getNumberProcessors());
		return processors;
	}
 
	// read only
	public void setProcessors(String processors) {
		this.processors = processors;
	}
 
	public MonitorManagedBean() {
    }
}

JSF Facelet XHTML presentation

Facelet index.xhtml
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core">
<!-- f:loadBundle basename="resources.application" var="bundle"/-->
<head>
    <title><h:outputText value="Collatz Distributed EE Application" /></title>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <meta http-equiv="Content-Style-Type" content="text/css"/>
    <meta http-equiv="expires" content="Wed, 26 Feb 1997 08:21:57 GMT"/>
 
</head>
<body text="#ffffff" bgcolor="#303030" link="#33D033" vlink="#D030D0" alink="#D03000">
<h3><h:outputText value="Collatz Distributed EE Application" /></h3>
<!-- p><h:outputText value="#{bundle['welcome.message']}" /></p-->
interval: <h:outputText value="#{monitorBean.interval}"/><br/>
currentNumber: <h:outputText value="#{monitorBean.currentNumber}"/><br/>
maxPath: <h:outputText value="#{monitorBean.maxPath}"/><br/>
maxValue: <h:outputText value="#{monitorBean.maxValue}"/><br/>
workUnits: <h:outputText value="#{monitorBean.workUnits}"/><br/>
MIPS: <h:outputText value="#{monitorBean.mips}"/><br/>
processors: <h:outputText value="#{monitorBean.processors}"/><br/>
 
</body>
</html>

SE Client

  • The SE client will need a reference to the EE libraries of the server, here are the locations of the relevant jars. In the case of GlassFish - the gf-client.jar' is a manifest only jar that references the other EE jars by relative paths (so do not move it). In the case of WebLogic - the wlfullclient.jar library must be generated at design-time.
    • GlassFish 3.0.x
      • C:\opt\nbglassfish301\glassfish\modules\gf-client.jar
    • GlassFish 3.1
      • C:\opt\nbgf31b41\glassfish\lib\gf-client.jar
    • WebLogic 10.3.4.0
      • c:\jdk1.6.0\bin\java -cp .;../resource/wlfullclient.jar org.eclipse.persistence.example.distributed.collatz.presentation.SEClient gx660a t3://192.168.0.199:7001
 

JNDI name for Remote Session Bean

  • For a bean named
@Stateless(name="CollatzFacade",mappedName="ejb/CollatzFacade") // mappedName for ejb3.0 only
  • The client must resolve the following name in GlassFish.
private static final String SESSION_BEAN_REMOTE_NAME 
 = "java:global/CollatzGF/CollatzGF-ejb/CollatzFacade!org.dataparallel.collatz.business.CollatzFacadeRemote";
  • The client must resolve the following name in WebLogic.
    private static final String SESSION_BEAN_REMOTE_NAME 
 = "ejb/CollatzFacade#org.eclipse.persistence.example.distributed.collatz.business.CollatzFacadeRemote";

SE Client Classpath

JNDI InitialContext Setup for GlassFish

  • The following line will setup the InitialContext properties properly for GlassFish when it is not run in the same JVM as GlassFish.
c:\jdk1.6.0-32b\bin\java -cp .;C:/opt/gf31b40/glassfish/lib/gf-client.jar;CollatzModel-jar.jar;CollatzGF-ejb.jar 
 -Djava.naming.factory.initial=com.sun.jndi.cosnaming.CNCtxFactory 
 -Dorg.omg.CORBA.ORBInitialHost=127.0.0.1  
 -Dorg.omg.CORBA.ORBInitialPort=3700 
 org.dataparallel.collatz.presentation.SEClient

JNDI InitialContext Setup for WebLogic

  • The following line will setup the InitialContext properties properly for weblogic either for remote or local access when it is not run in the same JVM as the server - or it is.
 c:\jdk1.6.0\bin\java -cp .;../resource/wlfullclient.jar org.eclipse.persistence.example.distributed.collatz.presentation.SEClient server_name t3://192.168.0.199:7001
  • We will fallback on the design-time code below if the JNDI parameters are not entered at runtime
public static String serverT3[] = {"t3://192.168.0.199:7001"};
private static final String CONTEXT_FACTORY_NAME = "weblogic.jndi.WLInitialContextFactory";

Distributed SE Client Source

public class SEClient {
    /** Maximum BigInteger that can be stored in SQL field NUMERIC = 0x7fffffffffffffffL or
     * 2^63 or 10^19 or 9,223,372,036,854,775,808 or 9 Quintillion.
     * Numbers greater than this are encountered in scientific, cryptographic and nanosecond time sensitive calculations. 
     */
    private static final Long MAX_BIGINTEGER_IN_SQL = Long.MAX_VALUE;
    /** Get number of (hyperthreaded + real) cores.  IE: p630 with HT=2, Core2 E8400=2 and Core i7-920 = 8 */
    public static final int CORES = 4;//Runtime.getRuntime().availableProcessors() << 0;
 
    // TODO: we need to move all this to a properties file
    /** this is the index of the current server - for use in the maps and lists below */
    public static int serverInUse = 0;
    public static String serverDNS[] = {"d010926"};
    /** RMI t3 URL */
    //public static String serverT3[] = {"iiop://127.0.0.1:3700"};
    //public static String serverT3[] = {"t3://192.168.0.193:7001"};
    public static String serverT3[] = {"t3://127.0.0.1:7001"};
    private int numberServers = serverDNS.length;
    /** list of server names from above arrays */
    private List<String> serverNames = new ArrayList<String>();
    /** Input context map hashtable entries - For JNDI we are forced to use Hashtable instead of HashMap*/
    private Map<String, Hashtable<String, String>> contextHashtableMap  = new HashMap<String, Hashtable<String, String>>();
    /** output cached context maps for each remote server */
    private Map<String, Context> rmiCachedContextMap = new HashMap<String, Context>();
    /** $Proxy remote objects */
    private List<Map<String, CollatzFacadeRemote>> remoteObjects = new ArrayList<Map<String, CollatzFacadeRemote>>(CORES);
    /** How many processors are available (real + hyperthreaded) */
    private Map<String, Integer> availableProcessors = new HashMap<String, Integer>();
    /** whether the node is accepting requests or not */
    private Map<String, Boolean> nodeUnavailable = new HashMap<String, Boolean>();
    /** map of t3 protocol URLs */
    private Map<String, String>  serverIPMap = new HashMap<String, String>();
 
    //private List<CollatzRunnable> runnables = new ArrayList<CollatzRunnable>();
 
    private static final String DEFAULT_CLIENT_NAME = "default";
    // WebLogic
    // verify that all EE libraries available via http://download.oracle.com/docs/cd/E12840_01/wls/docs103/client/jarbuilder.html
    private static final String CONTEXT_FACTORY_NAME = "weblogic.jndi.WLInitialContextFactory";
    private static final String SESSION_BEAN_REMOTE_NAME = "ejb/CollatzFacade#org.obrienlabs.distributed.collatz.business.CollatzFacadeRemote"; 
 
    public SEClient () {
        // initialize state
    	initialize();
    }
 
    private void initialize() {
        for(int i=0;i<numberServers;i++) {
            // For each server add the name key and corresponding RMI URL
            serverNames.add(serverDNS[i]);
            serverIPMap.put(serverDNS[i], serverT3[i]);
            nodeUnavailable.put(serverDNS[i], false);
            availableProcessors.put(serverDNS[i], Runtime.getRuntime().availableProcessors());
            Hashtable<String, String> aTable =  new Hashtable<String, String>();
            contextHashtableMap.put(serverDNS[i],aTable);
            aTable.put(Context.INITIAL_CONTEXT_FACTORY,CONTEXT_FACTORY_NAME);
            aTable.put(Context.PROVIDER_URL, serverT3[i]);
        }
        for(int i=0;i<CORES;i++) {
        	connect(i);
        }
    }
 
    public void connect(int threadID) {
        // Setup RMI Objects
        // Establish RMI connections to the session beans
        //for(String aServer : serverNames) {
    	String aServer = serverNames.get(0);
            Context aContext = null;
            try {
                // no need to set the host if on same machine
                aContext = new InitialContext(contextHashtableMap.get(aServer));
                rmiCachedContextMap.put(aServer, aContext);
                System.out.println("_collatz: " + System.currentTimeMillis() + ": Context for " + (aServer + threadID) + " : " + aContext);
                // For qualified name look for weblogic log "EJB Deployed EJB with JNDI name"
                Object aRemoteReference = aContext.lookup(SESSION_BEAN_REMOTE_NAME);
                System.out.println("_collatz: " + System.currentTimeMillis() + ": Remote Object: " + aRemoteReference);
                // narrow the $proxy remote bean
                CollatzFacadeRemote aNode = (CollatzFacadeRemote) PortableRemoteObject.narrow(aRemoteReference, CollatzFacadeRemote.class);
                Map<String, CollatzFacadeRemote> remoteObject = new HashMap<String, CollatzFacadeRemote>();
                remoteObject.put(aServer, aNode);
                remoteObjects.add(remoteObject);
            } catch (Exception ce) {
                // server down throws a javax.naming.CommunicationException inside a java.net.ConnectException
                ce.printStackTrace();
                // mark the current node as down, clear the flag in 5 min
            }
        //}
    }
 
    private CollatzFacadeRemote lookupRemoteBean(String aServer, int threadID)  {
        CollatzFacadeRemote remoteBean = null;
        try {
            Context aContext = rmiCachedContextMap.get(aServer);
            // For qualified name look for weblogic log "EJB Deployed EJB with JNDI name"
            Object aRemoteReference = null;
            boolean remoteLookupSuccess = false;
            int lookupIterations = 0;
            while(!remoteLookupSuccess && aRemoteReference == null && lookupIterations < 50) {
                System.out.println("_collatz: " + System.currentTimeMillis() + ": Thread: " + threadID + " : Context lookup for " + SESSION_BEAN_REMOTE_NAME + " from: " + aContext);
                try {
                    aRemoteReference = aContext.lookup(SESSION_BEAN_REMOTE_NAME);
                } catch (NameNotFoundException nnfe) {
                    System.out.println(nnfe.getMessage());
                    System.out.println("_collatz: " + System.currentTimeMillis() + ": retry session bean lookup - possible redeploy in progress on central server: " + lookupIterations);
                    Thread.sleep(1000);
                } catch (CommunicationException ce) {//SocketResetException sre) {
                	// Network was temporarily disconnected - or server went down
                    System.out.println(ce.getMessage());
                    System.out.println("_collatz: " + System.currentTimeMillis() + ": retry session bean lookup - Network or server is temporarily down: " + lookupIterations);
                    Thread.sleep(1000);
                }
                lookupIterations++;
            }
            System.out.println("_collatz: " + System.currentTimeMillis() + ": Remote Object: " + aRemoteReference);
            // narrow the $proxy remote bean
            remoteBean = (CollatzFacadeRemote) PortableRemoteObject.narrow(aRemoteReference, CollatzFacadeRemote.class);
            Map<String, CollatzFacadeRemote> remoteObject = new HashMap<String, CollatzFacadeRemote>();
            remoteObject.put(aServer, remoteBean);
            setRemoteObjects(threadID, remoteObject);
        /*} catch (ConnectException rmice) {//CommunicationException ce) {//SocketResetException sre) {
        	// Network was temporarily disconnected - or server went down
            System.out.println(rmice.getMessage());
            System.out.println("_collatz: " + System.currentTimeMillis() + ": retry session bean lookup - Network or server is temporarily down: " + lookupIterations);
            Thread.sleep(1000);*/
        } catch (Exception e) {
            e.printStackTrace();
        }
        return remoteBean;
     }
 
    public void processUnitOfWork(int threadID) {
        // ask for a work packet
        UnitOfWork uow = null;
        CollatzFacadeRemote collatzFacade = null;
        StringBuffer aBuffer = new StringBuffer();
        String threadName;
        // Endlessly generate RMI requests
        for(;;) {
            try {
            	// Send messages to entire grid in parallel if we connect to more than one server
            	// TODO: create Threads for each remoteObject
            	for(String remoteServer : remoteObjects.get(threadID).keySet()) {
            		threadName = serverDNS[serverInUse] + threadID;
            		collatzFacade = remoteObjects.get(threadID).get(remoteServer);
         			try {
           				// Issue: One JVM halt will affect the entire distributed app.
           				// don't let a node failure halt the host
           				// this remote call can throw an EJBException wrapping a java.rmi.ConnectException                            
           				uow = collatzFacade.requestUnitOfWork(threadName,availableProcessors.get(remoteServer)); 
           				if(null == uow) {
           					// possible redeploy
           					System.out.println("_collatz: " + System.currentTimeMillis() + ": persistence not functioning on server " + serverDNS[serverInUse]);
           				} else {
           					aBuffer = new StringBuffer("_collatz: ");
           					aBuffer.append(System.currentTimeMillis());
           					aBuffer.append(": processing UnitOfWork: ");
           					aBuffer.append(uow);
           					aBuffer.append(" ID#");
           					aBuffer.append(uow.getId());
           					aBuffer.append(" ");
           					aBuffer.append(uow.getInitial());
           					aBuffer.append("-");
           					aBuffer.append(uow.getExtent());
           					aBuffer.append(" for: ");
           					aBuffer.append(threadName);
           					System.out.println(aBuffer.toString());
           				}
           			} catch (Exception e) {//(EJBException e) {
           				//  weblogic.transaction.internal.TimedOutException: Transaction timed out after 29 seconds
           				// or SQLException on constraint violation
           				// EJBException wrapping a java.rmi.ConnectException if the server is not running
           				e.printStackTrace();
           				// mark the current node as down, clear the flag in 5 min
           				//nodeUnavailable.put(remoteServer, true);
           			}
            		// compute collatz for the sequence
            		uow.processInterval();
            		Thread.yield(); // 
            		// return the results to the server
            		// don't cache the remote bean (it may be GC'd or redeployed)
            		collatzFacade = lookupRemoteBean(remoteServer, threadID);
            		boolean retry = true;
            		while(retry) {
            			try {
            				collatzFacade.postUnitOfWork(uow,retry); // possible EJBException or OptimisticLockException
            				retry = false;
            			} catch (Exception ole) {//OptimisticLockException ole) {
            				//System.out.println(ole.getMessage());
            				retry = true;
            				Thread.sleep(1000);
            			}
            		}
            		aBuffer = new StringBuffer("_collatz: ");
            		aBuffer.append(System.currentTimeMillis());
            		aBuffer.append(": results sent to server after ");
            		aBuffer.append(uow.getEndTimestamp() - uow.getStartTimestamp());
            		aBuffer.append(" ms @ ");
            		aBuffer.append(uow.getMIPS());
            		aBuffer.append(" MIPS");
            		System.out.println(aBuffer.toString());
            	}
            } catch (Exception e) {
            	e.printStackTrace();
            	try {
            		Thread.sleep(10000);
            	} catch (Exception ex) { }
            }
        }
    }
 
    protected void startThreads(int numberOfThreads) {
        threadSafetyPrivate(numberOfThreads);
    }
 
    protected void threadSafetyPrivate(int numberOfThreads) {
        List<Thread> threadList = new ArrayList<Thread>();
        for(int i=0; i<numberOfThreads; i++) {
            Thread aThread = new Thread(new CollatzRunnable(i));
            threadList.add(aThread);
            // stagger the threads so they are not in lockstep
            try {
            	Thread.sleep(3000);
            } catch (Exception e) {           }
            aThread.start();
        }
 
        // Wait for [threadNumber] threads to complete before ending 
        for(Thread aThread : threadList) {
            try {
                synchronized (aThread) {
                	aThread.join();
                }
            } catch (InterruptedException ie_Ignored) {
            	ie_Ignored.printStackTrace();
            } // The InterruptedException can be ignored 
        }
    }
 
    // Inner class implements Runnable instead of extending Thread directly
    class CollatzRunnable implements Runnable {
    	protected int id;
 
    	public CollatzRunnable(int anId) {
    		id = anId;
    	}
 
        public void run() {
        	//connect(id);
            // We loop an arbitrary number of iterations inside each thread
            processUnitOfWork(id);
        }
    }
 
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        String clientName = null;
        String ip = null;
        if(null != args && args.length > 0) {
            clientName = args[0];
            ip = args[1];
        } else {
            clientName = DEFAULT_CLIENT_NAME;
            ip = "t3://127.0.0.1:7001";  // weblogic specific                       
        }
        serverDNS[serverInUse] = clientName;
        serverT3[serverInUse] = ip;
 
        SEClient client = new SEClient();
        // Create and start threads
        client.startThreads(CORES);
        //client.connect();
        //client.processUnitOfWork();
    }
 
    public Map<String, Hashtable<String, String>> getContextMap() {        return contextHashtableMap;    }
    public void setContextMap(Map<String, Hashtable<String, String>> contextMap) {        this.contextHashtableMap = contextMap;    }
    public Map<String, Context> getRmiContextMap() {        return rmiCachedContextMap;    }
    public void setRmiContextMap(Map<String, Context> rmiContextMap) {        this.rmiCachedContextMap = rmiContextMap;    }
    public Map<String, CollatzFacadeRemote> getRemoteObjects(int threadID) {        return remoteObjects.get(threadID);    }
    public void setRemoteObjects(int threadID, Map<String, CollatzFacadeRemote> remoteObjects) {        this.remoteObjects.set(threadID, remoteObjects);    }
    public Map<String, Boolean> getNodeUnavailable() {        return nodeUnavailable;    }
    public void setNodeUnavailable(Map<String, Boolean> nodeUnavailable) {        this.nodeUnavailable = nodeUnavailable;    }
    public Map<String, String> getServerIPMap() {        return serverIPMap;    }
    public void setServerIPMap(Map<String, String> aServerIPMap) {        serverIPMap = aServerIPMap;    }
    public List<String> getServernames() {        return serverNames;    }
    public void setServerNames(List<String> serverNames) {        this.serverNames = serverNames;    }
    public int getNumberServers() {        return numberServers;    }
    public void setNumberServers(int numberServers) {        this.numberServers = numberServers;    }
    public Map<String, Integer> getAvailableProcessors() {        return availableProcessors;    }
    public void setAvailableProcessors(Map<String, Integer> availableProcessors) {        this.availableProcessors = availableProcessors;    }
}

WebService Client

JMS Client

JAX-RS Client

JSF 2.0 Client

Hardware

  • There are 3 networks that I am simulating on (two at work, one at home) with a total of 23 cores available.
    • W1: Production NOC off-grid subnet (7 threads total)
      • One 4-core Q6600 server (with up to 3 threads for local clients)
      • Four 1-core P630 clients (with up to 8 threads - but in practice I run the CPU's at 50% with a single thread)
    • W2: Development NOC (3 threads total)
      • One 2-core E8400 server (with up to 1 thread for local clients)
      • One 2-core SunFire (near Corei7-920 performance) client (with 2 threads)
    • H1: Development NOC (13 threads total)
      • One 4-core Corei7-920 server (with up to 6 threads for local clients)
      • Four 1-core P630 clients (with up to 8 threads - but in practice I run the CPU's at 50% with a single thread)
      • One 2-core T4400 client (1 thread)
      • One 2-core E8400 client (1 thread)
      • One 1-core P520 client (1 thread)
  • Here is a screencap of the H1 NOC with 13 threads on 8 physical machines.

20110213 collatz proto cluster screen cap 12 threads 2.JPG

Configuring TCPIP Traffic between 2 Networks

  • When you are developing distributed and even multithreaded applications using a separate cluster. This is usually done on a private network like 192.168.n.N. There are a lot of reasons why your cluster may need to be off your corporate network - usually it comes down to the fact that your private router cannot or should not be directly connected to the rest of the network. In most cases your primary development PC will act as a bridge between the corporate and development network and require two network interfaces. This is usually accomplished by adding a 2nd network card to your pc or using a wireless dongle and connecting wirelessly to the private router.
  • Issues will arise surrounding packet routing when both networks are live. Occassionaly you will luck out and one of the network interfaces will correctly route external HTTP traffic to the right network - but this will not always work because of the variable nature of TCP/IP packet routing tables.
  • The solution is to override the interface metric setting off the Network properties | Internet Protocol (TCP/IP) | Internet Protocol (TCP/IP) properties | Advanced | Advanced TCP/IP Settings | Automatic metric. Usually this metric is set to checked=automatic and the metric - partially depending on the route length statistic will be variable.
    • For each physical/wireless network - change this number to manual and enter something like 100 and 200 for the two networks - where a smaller number represents the network that should be used first.
    • If you set the private network from DHCP to static and set the gateway and DNS servers to your private router then everything will function correctly. Your internet and intranet traffic will go through your corporate gateway and your private cluster traffic 192.168.n.N will go through your private network. You will not get a page not found anymore.

Dual tcpip networks config.JPG Dual tcpip networks config one local.JPG Dual tcpip networks set automatic metric.JPG Dual subnets in ie.JPG

Performance

  • We need to set a baseline and test various simulations in 2 or more variables so that we get the optimum configuration before we start a large run that could last for months.

Performance Criteria

  • The following criteria will be used to optimize the distributed performance of the application.

Multicore Usage

  • All the current CPU's available have 1,2,3 or 4 cores. Some of them are hyperthreaded.
  • Q) Do we really get N times performance gain if we use up all the hardware cores? The answer is yes below
  • A) If we look at a dual-core (non-HT) system we get a 76% speedup with a 12% drop in performance per core if both are used.

Analysis: Multicore

  • 2 threads on a dual-core E8400 (100% cpu)
_collatz: threads,Interval,start,end:    2,8388607,687865859,696254466
_collatz: Remote Object: ClusterableRemoteRef(1986630296164710704S:10...:[7001,7001,-1,-1,-1,-1,-1]:base_domain:AdminServer [1986630296164710704S:10...:[7001,7001,-1,-1,-1,-1,-1]:base_domain:AdminServer/314])/314
_collatz: results sent to server after 77720 ms
_collatz: process UnitOfWork: org.eclipse.persistence.example.distributed.collatz.model.UnitOfWork@87( id: 87) ID#87 654311427-662700034 from: gx660a
_collatz: threads,Interval,start,end:    2,8388607,696254467,704643074
_collatz: Remote Object: ClusterableRemoteRef(1986630296164710704S:10...:[7001,7001,-1,-1,-1,-1,-1]:base_domain:AdminServer [1986630296164710704S:10...:[7001,7001,-1,-1,-1,-1,-1]:base_domain:AdminServer/314])/314
_collatz: results sent to server after 78142 ms
_collatz: process UnitOfWork: org.eclipse.persistence.example.distributed.collatz.model.UnitOfWork@92( id: 92) ID#92 696254467-704643074 from: gx660b
  • 1 thread on a dual-core E8400 (50% cpu)
_collatz: threads,Interval,start,end:    2,8388607,771751939,780140546
_collatz: Remote Object: ClusterableRemoteRef(1986630296164710704S:10.156.52.246:[7001,7001,-1,-1,-1,-1,-1]:base_domain:AdminServer [1986630296164710704S:10.156.52.246:[7001,7001,-1,-1,-1,-1,-1]:base_domain:AdminServer/314])/314
_collatz: results sent to server after 68781 ms
_collatz: process UnitOfWork: org.eclipse.persistence.example.distributed.collatz.model.UnitOfWork@104( id: 104) ID#104 796917763-805306370from: gx660b
  • We see a 12% drop of performance on both hardware cores if both are active. However that also means a 76% increase in performance when both hardware cores are used.

Multithreaded Usage

  • For each core above we may have hyperthreading available - it was available on the Pentium IV and again on the Corei7-9xx. The question is what is the optimum number of threads? I would expect that we need to determine how many soft cores the machine has (hard + hyperthreaded) and not use more than this number. This would be 2 for a single core P4-630, 2 for a dual core E8400 or T4400, 4 for a quad core Q6600 and 8 for a Corei7-920.
  • The other question is do we really get a performance gain when using the hyperthreaded cores. Or if I use 2 threads on a single core P-630 or 8 threads on a Corei7-920 what is the performance gain? From initial experimentation it looks like we can get up to a 50% performance increase by using the HT cores. Use of the HT cores will slow down the hard cores as both share cache and ram. Therefore if the algorithm was kept in register memory we would be able to use the superscalar execution queues.

Multicore Analysis

  • Linked from Performance page.
  • A problem that maps very well to multiple cores and is easily parallelized is the computation of the Mandelbrot set.
  • The following graph is the result of an experiment where I varied the number of cores used to render each frame of a deep zoom to the limit of double floating point precision. When I run this algorithm as a traditionally single threaded application it takes up to 800 seconds to render a 1024x1024 grid from 1.0 to 1 x 10^-16. However when I start adding threads I see the best speedup when I use the same number of threads as there are 'hard processors (non-hyperthreaded). The performance increase nears it's maximum 8 times increase for an Intel Corei7-920 when I approach a thread/line of 512 threads.

Corei7 920 zoom time 1 to 512 threads graph.JPG

  • As you can see from the graph, we benefit more from a massive number of threads - as long as they are independent. The Mandelbrot calculation however it not homogeneous - computing the central set requires a lot more iteration than outlying areas. This is why each parallel algorithm must be fine tuned to the problem it is solving. If you look at the screen captures of performance during the runs with various thread counts you will see what I mean. The processor is not being exercised at it's maximum capacity when the bands assigned to particular threads are finished before other threads that are performing more calculations than their peers. If we increase the number of bands - we distribute the unbalanced load among the cores more evenly - at a slight expense of thread coordination/creation/destruction.

Multicore Rendering of Mandelbrot Set

  • The following runs are on a 1024x1024 grid starting form 1.0 to 0.0000000000000001 that take from 800 to 67 seconds depending on the number of threads used concurrently. Notice that I have a temporary issue with shared variable access between threads - as some of the pixel coloring is off.
  • As you can see - the processor usage goes from 12% for a single core, through 50% for 8 cores - to 100% for 128+ cores.
  • Why do we need so many threads? If even one thread takes longer than any other ones that are already completed their work unit - the entire computation is held up. We therefore use more work units than there are threads.
  • A better algorithm would be to distribute work units asynchronously instead in the current MapReduce synchronous way we currently use. When a thread is finished, it can work on part of the image that is still waiting processing. We would need to distribute work units more like packets in this case.
  • 1 thread on an 8-core i7-920 takes 778 sec

Corei7 920 grid1024 zoom 1 thread.JPG

  • 2 threads on an 8-core i7-920 takes 466 sec

Corei7 920 grid1024 zoom 2 thread.JPG

  • 16 threads on an 8-core i7-920 takes 138 sec

Corei7 920 grid1024 zoom 16 thread.JPG

  • 128 threads on an 8-core i7-920 takes 114 sec

Corei7 920 grid1024 zoom 128 thread.JPG

Thread Contention for Shared Resources

  • For our multithreaded Mandelbrot application - which currently is not @ThreadSafe - we encounter resource contention specific to the Graphics context. This type of contention is the same for any shared resource such as a database. The issue is that setting a pixel on the screen is not an atomic operation - it consists of setting the current color and then drawing the pixel (The Java2D API may require multiple internal rendering steps as well). The result of this is that another thread may change the color of the graphics context before the current thread actually writes the pixel - resulting in noise - or more accurately - Data Corruption.

Debug notthreadsafe function due to gcontext color changed mid pixel write.jpg

  • Note: that no noise or data corruption occurs when we run a single thread. We only get a problem when we run multiple threads concurrently.
 color = Mandelbrot.getCurrentColors().get(iterations);
 color2 = color;
 
 // these 2 lines need to be executed atomically - however we do not control the shared graphics context
 synchronized (color) { // this does not help us with drawRect()
    mandelbrotManager.getgContext().setColor(color);
    // drawRect is not atomic, the color of the context may change before the pixel is written by another thread
    mandelbrotManager.getgContext().drawRect((int)x,(int)y,0,0);
 }
 if(color2 != mandelbrotManager.getgContext().getColor()) {
    System.out.println("_Thread contention: color was changed mid-function: (thread,x,y) " + threadIndex + "," + x + "," + y);
    // The solution may be to rewrite the pixel until the color is no longer modified
 }
 
_Thread contention: color was changed mid-function: (thread,x,y) 2,298,22
_Thread contention: color was changed mid-function: (thread,x,y) 15,140,155
_Thread contention: color was changed mid-function: (thread,x,y) 15,140,156
_Thread contention: color was changed mid-function: (thread,x,y) 15,140,157
_Thread contention: color was changed mid-function: (thread,x,y) 15,141,151
_Thread contention: color was changed mid-function: (thread,x,y) 2,307,25
_Thread contention: color was changed mid-function: (thread,x,y) 15,143,154
_Thread contention: color was changed mid-function: (thread,x,y) 15,144,152
_Thread contention: color was changed mid-function: (thread,x,y) 13,0,130
_Thread contention: color was changed mid-function: (thread,x,y) 11,0,110
  • The better solution would be designate a host thread that coordinates all the unit of work threads and acts as a single proxy to the GUI - only one thread should update AWT or Swing UI elements - as most of them are not thread safe by design. Multithreaded distributed applications need to be very careful when using GUI elements. For example if I do not introduce at least a 1ms sleep between GUI frames - the entire machine may lock up when 100% of the CPU is given to the calculating threads.

Local vs remote threads

  • If I use 4 threads on a 4 core chip like the 920 or 4 single threads on 4 separate P630 machines - what kind of gain do I see?

Network Bandwidth

  • Not an issue.
  • We are using Gigabit ethernet. So far because of the low data transmission of our application I rarely see the network go above 1%.

Preliminary Performance Numbers

  • There are 3 networks that I am simulating on (two at work, one at home) with a total of 23 cores available. The standard work packet at this point is 2^23 numbers - or 8,388,608. Processing these 8 million numbers takes a range of 55 to 330 seconds depending on the processor for numbers below 12 billion.
  • The processing time for 23 bits for P4-630 class vintage pentium IV processors running the latest SUN 32-bit 1.6.0_23 JVM ranges from 110 to 230 seconds.
  • The best processing time for the 64-bit SUN JVM (which although it has 4-5 times better Long scalar processing - does not help with BigInteger) is around 70 seconds.
  • For some reason I get varying numbers for different machines running the same JVM on the same Chip with a variance of up to 50% that shouldn't be due to RAM/HD differences.

Performance Summary

MHz MIterationsPS 1-core MIterationsPS n-core Chip Cores Hard/HT Tech Library Bits
2800 Core i7-920 8,4/4 Java long 64
2800 Core i7-920 8,4/4 Java BigInteger unlimited
2800 Core i7-920 8,4/4 C++ __int64 VS10 64
2800 Core i7-920 8,4/4 x86 Assembly user 64
400 0.2 MIPS 0.8 MIPS XMOS G4 32 4/28 XC user 32
80@20 MIPS 2.3 MIPS 18.4 MIPS Parallax P8X32 8 8/0 Assembly user 32

Java 64-bit long

Java BigInteger

    public long processInterval() {
        StringBuffer buffer = null;
        long operations = 0L;        
        BigInteger pathIteration = BigInteger.ONE;//, path should fit in 64 bits
        BigInteger maxValueIteration = BigInteger.ONE;
        boolean milestone = false;
        List<BigInteger> list = new ArrayList<BigInteger>();        
        BigInteger currentNumber = initial;
        CollatzRecord record = null;
        setStartTimestamp(System.currentTimeMillis());
        while (currentNumber.compareTo(extent) < 0) {
        	list = hailstoneSequenceUsingBigInteger(list, currentNumber);
            // cache maxPath and maxValue for performance : were doing 10000 times the iterations required
            if(!list.isEmpty()) {
                maxValueIteration = list.remove(0);
                pathIteration = list.remove(0);
                operations = operations + pathIteration.longValue();
                // keep track of local milestones
                if(pathIteration.compareTo(getMaxPath()) > 0) {                    
                    milestone = true;
                    record = new CollatzRecord();
                    record.setPathLength(pathIteration);
                    record.setMaximum(maxValueIteration);
                    setMaxPathRecord(record);
                    record.setIsPathRecord(true);
                }
                if(maxValueIteration.compareTo(getMaxValue()) > 0) {                    
                    if(!milestone) {
                        record = new CollatzRecord();
                        record.setPathLength(pathIteration);
                        record.setMaximum(maxValueIteration);
                        milestone = true;
                    }
                    setMaxValueRecord(record);
                    record.setIsMaxRecord(true);
                }
                if(milestone) {
                    record.setUnitOfWork(this);
                    record.setInitial(currentNumber);
                    addRecord(record);
                    milestone = false;
                }
            }
            currentNumber = currentNumber.add(TWO);
        }
        setEndTimestamp(System.currentTimeMillis());
        setOperations(operations);
        return operations;   
    }

VS10 C++ x86

  • For the $4400 dual-core 2Ghz Intel processor running optimized 64-bit C we get 580,000 sequences/second (0.6 MSPS) using 64-bit long data types in C using a single core.
    • 29 seconds for a 24 bit search which comes out to 18 trillion/year per core

Vs2010 compiler optimization sse 2.JPG

    __int64 hailstoneMax(__int64 start) {
    __int64 maxNumber = 0;
    __int64 num = start;
    __int64 path = 0;
    while(num > 4) {
    	if((num % 2) > 0) {
    		//num = (num >> 1) + num + 1; // odd (combined 2-step odd/even = 30% speedup
		num = (num << 1) + num + 1; // odd
    	} else {
    		num >>= 1; // even
    	}
    	if(num > maxNumber) {
    		maxNumber = num;
    	}
	path++;
    }
    return maxNumber;
}
 
void getSequence64() {
  __int64 num = 27;
  __int64 maxNumber = 0;
  __int64 newMax = 0;
  unsigned long long path = 0;
  unsigned long long maxPath = 0;
  __int64 MAX = (1 << 30); // dont use long long
  while(1) {//num < MAX) {
	newMax = hailstoneMax(num);	
	if(newMax > maxNumber) {
		printf("\n%I64d,\t%I64d",num, newMax);
		maxNumber = newMax;
	}
	num += 2;
  }
}

VS10 SSE x86

VS10 ATI GPU

XMOS XC

  • For the XS-1A G4 4-core/32-thread 32-bit processor we get 200,000 sequences/second (0.2 MSPS) using 32-bit long data types in XC using a single core.
    • 85 seconds for a 24 bit search which comes out to 6.3 trillion/year
#include <platform.h>
#include <print.h> //http://www.xmos.com/discuss/viewtopic.php?f=6&t=255
#define PERIOD 20000000
 
/**
 * http://en.wikipedia.org/wiki/XSwitch#XS1-G4_Switch
 */
unsigned long number = 27;
unsigned long maximum = 1 << 18;//138367; // 32 bit registers top out at a 4 billion max for 138367
 
// Compute the hailstone maximum
unsigned long hailstoneMax(unsigned long start) {
	unsigned long maxNumber = 0;
    unsigned long number = start;
    while(number > 1) {
    	if((number % 2) > 0) {
    		number = (number << 1) + number + 1; // odd
    	} else {
    		number = number >> 1; // even
    	}
    	if(number > maxNumber) {
    		maxNumber = number;
    	}
    }
	return maxNumber;
}
 
void hailstoneSearch(int coreId, out port led, unsigned long start, unsigned long end) {
  unsigned long number = 27;
  unsigned long maxNumber = 0;
  unsigned long newMax = 0;
  int flip = 0;
  while(number < end) {
	  newMax = hailstoneMax(number);
	  if(newMax > maxNumber) {
		maxNumber = newMax;
		// TODO: send message to other cores
		// UART printing really slows down the cores
		/*if(coreId < 1) { // only core 0 prints
			printuint(number);
			printchar(',');
			printchar('\t');
			printuintln(maxNumber);
		}*/
		if(flip > 0) {
			flip = 0;
			led <: 0b1111;
		} else {
			flip = 1;
			led <: 0b0000;
		}
	  }
	number = number + 2;
  }
  printint(coreId); // print core id when finished
}
 
// Search a range of integers for their hailstone maximums
void hailstoneSearch0(int coreId, out port led, out port redCathode, out port greenCathode,
		unsigned long start, unsigned long end) {
	  redCathode <: 0b1111;
	  printuint(start);
	  printchar('-');
	  printuintln(end);
	  hailstoneSearch(coreId, led, start, end);
	  // reduce temperature by lowering the PLL multiplier
	  write_pswitch_reg(get_core_id(), XS1_PSWITCH_PLL_CLK_DIVIDER_NUM, 0x80);
	  while(1);
}
 
int main() {
	// concurrent threads p.33 http://www.xmos.com//system/files/xcuser_en.pdf
	par {
		on stdcore [0]: hailstoneSearch0(0, cled0,cledR,cledG,number, maximum);
		on stdcore [1]: hailstoneSearch(1, cled1,number, maximum);
		on stdcore [2]: hailstoneSearch(2, cled2,number, maximum);
		on stdcore [3]: hailstoneSearch(3, cled3,number, maximum);
	}
	return 0;
}

Parallax Assembly

  • This 32-bit scalar controller runs at 20 MIPS / core = 160 MIPS for 8 cores ( only 1/100th of p4 per core) - however I am able to load (in near SIMD fashion) this routine on all 8 cores of my grid of 80 propeller chips to achieve a theoretical speed of 20 x 80 x 4 = 6400 MIPS.
  • This is my first non-optimized machine language routine for the propeller chip - it is a testament to the tutorial by deSilva that I was running in 2 hours from never having written assembly since the 80386, the 8085(TRS-80 M-100) and the 6809E (TRS-80 COCO).
  • We get a raw performance of 2.3 million iterations/sec for 32-bit precision scalar arithmetic running machine language at PLL16 or 80Mhz or 20 MIPS per core. We therefore should get close to 18 MIPS per chip or 1500 MIPS for a 640 core compute grid.
CON
  _clkmode = xtal1 + pll16x
  _xinfreq = 5_000_000
VAR
  ' shared display RAM for the 4 display cogs
  long  buffer[32]                                         
  long  Stack0[64]                                         ' Stack Space
  byte  Cog[7]                                             ' Cog ID
  long  randomNum
  long  range
  long  aCounter
OBJ
  SER  : "Parallax Serial Terminal.spin"                   ' part of the IDE library  
  STR  : "STREngine.spin"                                  ' in the current directory
PUB main | milestone,start,number,index, lRec,x,i, mIndex, mValue, path,height, maxPath, maxHeight
  ' wait for user to switch to terminal
  waitcnt((clkfreq * 5) + cnt)
  maxPath := 0
  maxHeight := 0
  milestone := 0 ' track whether we got a path or max height hit
  range := 1 << 17
  ser.Start(115_200)'31,30,0,38400)
  ser.Home
  ser.Clear
  ser.Str(string("Collatz Conjecture", ser#NL))
  aCounter := 27
  _nextVal := @aCounter ' pass word to assembly via hub ram
  ser.Str(string("Max Value for "))
  ser.Str(STR.numberToDecimal(aCounter,8))
  ser.Str(string(" is "))
  Cog[1] := cognew(@entry, @aCounter) + 1
  waitcnt((clkfreq * 2) + cnt)  ' it takes 100ms to load a core
  ser.Str(STR.numberToDecimal(aCounter,8))
DAT
              org    0
entry         ' compute the max path/value for a particular Collatz number
              RDLONG    _nextVal, PAR       ' read from shared ram (7-22 cycles)
:iterate
              ADD       _path, #1           ' increment path
              MOV       _bit0, #1           ' create mask
              AND       _bit0, _nextVal WZ  ' check bit 0 - affect zero flag
              IF_NE JMP #:mul3x1
:div2         ' if even we divide by 2
              SHR       _nextVal, #1        ' divide by 2
              CMP       _nextVal, #1 WZ     ' check for 1 value == finished
              IF_E JMP  #:finish              
              JMP       #:iterate           ' return to top of loop
:mul3x1       ' if odd we transform by 3n + 1
              MOV       _3rdVal, _nextVal
              SHL       _nextVal, #1        ' multiply by 2
              ADD       _nextVal, _3rdVal   ' add to multiply by 3
              ADD       _nextVal, #1        ' add 1
:maxValue     ' check for maximum value
              MIN       _maxVal, _nextVal   ' VERY ODD (max is actually min)
              JMP       #:iterate           ' return to top of loop
:finish
              SUB       _path, #1           ' we discount the first path count
              MOV       _nextVal, _maxVal   ' copy maxVal to return value
              WRLONG    _nextVal, PAR       ' write back to hub ram (thank you deSilva for reverse flow explanation)
:endlessLoop
              JMP       #:endlessLoop       ' keep the cog running
_3rdVal       long   $00000000
_nextVal      long   $00000000      
_maxVal       long   $00000000
_path         long   $00000000
_bit0         long   $00000000
              FIT    496                     ' deSilva (16 I/O registers in 496-511 reserved)

Management and Reporting

  • There are formal tools and methods we can use to measure performance and also track and modify parameters of our application and the EE frameworks it runs on - at runtime.

JRockit Mission Control

  • When we run on an Oracle JRockit JVM - either on the server, the client or both - we have a lot of tools at our disposal. The key is JRMC.exe or JRockit Mission Control. JRMC enables us to use JMX MBeans exposed by WebLogic and the JPA provider - EclipseLink.

JRMC Method Profiler

  • The method profiler of JRMC is just one of the tools we can use when running JRockit to determine where our performance issues are - or test out a performance fix before and after a change.
  • For example - one of the biggest performance issues is toString() String.class allocation or concatenation. We should be able to answer the question of whether use of StringBuffer will alleviate this performance hit.

Jrockit mission control runtime adv method profiler shows weaved instrumented methods on entity sort by time.JPG

JMX Management

  • We can use the JMX MBeans exposed by the JPA provider - EclipseLink - to view and modify attributes of our persistence context running on the central server at runtime.
  • For instance, we may wish to change the logging level of the persistence context so we can temporarily track SQL statements to the database - without redeploying the EAR.
  • Any of JConsole or JRMC can be used - we will concentrate on JRMC or JRockit Mission Control.

Testing

Deployment

WebLogic 10.3.4.0 Server Logs

####<10-Feb-2011 12:42:25 o'clock PM VET> <Info> <EJB> <mfobrien-pc2> <AdminServer> <[ACTIVE] ExecuteThread: '5' for queue: 'weblogic.kernel.Default (self-tuning)'> <<WLS Kernel>> <> <> <1297357945922> <BEA-014022> <******** org.eclipse.persistence.example.distributed.collatz.business.CollatzFacadeRemote is bound with JNDI name:ejb/CollatzFacade#org.eclipse.persistence.example.distributed.collatz.business.CollatzFacadeRemote ********> 
####<10-Feb-2011 12:42:25 o'clock PM VET> <Info> <EJB> <mfobrien-pc2> <AdminServer> <[ACTIVE] ExecuteThread: '5' for queue: 'weblogic.kernel.Default (self-tuning)'> <<WLS Kernel>> <> <> <1297357945922> <BEA-010009> <EJB Deployed EJB with JNDI name org_eclipse_persistence_example_distributed_CollatzEARorg_eclipse_persistence_example_distributed_CollatzEJB_jarCollatzFacade_Home.> 
...
[EL Fine]: 2011-02-10 14:07:31.718--ServerSession(23787511)--Connection(23714705)--Thread(Thread[[ACTIVE] ExecuteThread: '16' for queue: 'weblogic.kernel.Default (self-tuning)',5,Pooled Threads])--SELECT ID, CATEGORY, RANK, THREADS, PERFORMANCE, IDENT, CORES, VERSION, ACTIVEUNITOFWORK_ID FROM ACTIVEPROCESSOR WHERE (IDENT = ?)
	bind => [xps435]
_collatz: requestUnitOfWork(2442133504-2443182080)
[EL Finest]: 2011-02-10 14:07:31.718--UnitOfWork(16753671)--Thread(Thread[[ACTIVE] ExecuteThread: '16' for queue: 'weblogic.kernel.Default (self-tuning)',5,Pooled Threads])--PERSIST operation called on: org.dataparallel.collatz.business.UnitOfWork[id=null].
[EL Finest]: 2011-02-10 14:07:31.718--UnitOfWork(16753671)--Thread(Thread[[ACTIVE] ExecuteThread: '16' for queue: 'weblogic.kernel.Default (self-tuning)',5,Pooled Threads])--assign sequence to the object (848 -> org.dataparallel.collatz.business.UnitOfWork[id=null])
[EL Fine]: 2011-02-10 14:07:31.718--ClientSession(21885694)--Connection(9190088)--Thread(Thread[[ACTIVE] ExecuteThread: '16' for queue: 'weblogic.kernel.Default (self-tuning)',5,Pooled Threads])--INSERT INTO UNITOFWORK (ID, STARTTIMESTAMP, VERSION, MAXPATH, EXTENT, INITIAL, RETRIES, ENDTIMESTAMP, MAXVALUE, KNOWNMAX_ID, PROCESSOR_ID, KNOWNPATH_ID) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
	bind => [848, 1297363051718, 1, 993, 2443182080, 2442133505, 0, null, 7125885122794452160, 846, 1, 847]
Processing UOW: org.dataparallel.collatz.business.UnitOfWork[id=848]

GlassFish 3.1 Server Logs

  • The EE server is the same for all test cases below

Server Logs

INFO: GlassFish Server Open Source Edition 3.1-b41 (41) startup time : Felix (7,984ms), startup services(750ms), total(8,734ms)
INFO: EclipseLink, version: Eclipse Persistence Services - 2.2.0.v20110202-r8913
CONFIG: Connected: jdbc:derby://localhost:1527/collatz2
INFO: file:/C:/_Netbeans691Projects/CollatzGF/dist/gfdeploy/CollatzGF/CollatzGF-ejb_jar/_CollatzGF-ejbPU login successful
INFO: Portable JNDI names for EJB CollatzFacade : [java:global/CollatzGF/CollatzGF-ejb/CollatzFacade, java:global/CollatzGF/CollatzGF-ejb/CollatzFacade!org.dataparallel.collatz.business.CollatzFacadeRemote]
INFO: Glassfish-specific (Non-portable) JNDI names for EJB CollatzFacade : [ejb/CollatzFacade, ejb/CollatzFacade#org.dataparallel.collatz.business.CollatzFacadeRemote]
...
FINER: client acquired: 16292112
FINER: TX binding to tx mgr, status=STATUS_ACTIVE
FINER: acquire unit of work: 30797517
...
INFO: Creating new org.dataparallel.collatz.business.ActiveProcessor[id=1]
FINE: INSERT INTO ACTIVEPROCESSOR (ID, CATEGORY, CORES, IDENT, PERFORMANCE, RANK, THREADS, VERSION, ACTIVEUNITOFWORK_ID) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
        bind => [1, 0, null, xps435, null, null, 2, 1, null]
INFO: _collatz: requestUnitOfWork(2147483648-2148532224)
FINE: INSERT INTO UNITOFWORK (ID, ENDTIMESTAMP, EXTENT, INITIAL, MAXPATH, MAXVALUE, RETRIES, STARTTIMESTAMP, VERSION, KNOWNMAX_ID, KNOWNPATH_ID, PROCESSOR_ID) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        bind => [5, null, 2148532224, 2147483649, 1, 1, 0, 1297284064640, 1, 3, 4, 1]
FINE: INSERT INTO PARAMETERS (ID, BESTITERATIONSPERSECOND, GLOBALDURATION, GLOBALSTARTTIMESTAMP, MAXPATH, MAXVALUE, NEXTNUMBERTOSEARCH, PARTITIONLENGTH, VERSION) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
        bind => [2, null, null, 1297284064640, 1, 1, 2148532224, 1048576, 1]
INFO: Processing UOW: org.dataparallel.collatz.business.UnitOfWork[id=5]
FINER: client acquired: 15148865
...
FINEST: Connection released to connection pool [read].
INFO: New max path : 967
INFO: New max value: 6694105047793216

TC1: Remote Session Bean Lookup on GlassFish 3.1 from SE Client on Same JVM

  • As you can see above, we let a single remote client run for a couple seconds so that it reported a completed UnitOfWork entity packet back to the session bean that holds the dependency injected persistence context on the server.

SE Client Logs

run:
9-Feb-2011 4:11:01 PM com.sun.enterprise.v3.server.CommonClassLoaderServiceImpl findDerbyClient
Context for xps435 : javax.naming.InitialContext@18fd984
INFO: Cannot find javadb client jar file, derby jdbc driver will not be available by default.
Remote Object: org.dataparallel.collatz.business._CollatzFacadeRemote_Wrapper@498cb673
Narrowed Session Bean: org.dataparallel.collatz.business._CollatzFacadeRemote_Wrapper@498cb673
UnitOfWork from: xps435 = org.dataparallel.collatz.business.UnitOfWork[id=5] 2147483649-2148532224
Range:       2147483649 to: 2148532224
M,1048575,0,2148041982,693        ,6694105047793216        ,8666        ,
P,1048575,0,2148398424,967        ,966616035460        ,14359        ,
UnitOfWork from: xps435 = org.dataparallel.collatz.business.UnitOfWork[id=8] 2148532225-2149580800
Range:       2148532225 to: 2149580800
BUILD STOPPED (total time: 27 seconds)

TC2:Remote Session Bean Lookup on GlassFish 3.1 from SE Client on Different JVM - Local Machine

SE Client Logs

  • This naming error is all over the internet - it means that the JNDI bean name below was not found (this exact code without the InitialContext parameters work when run inside NetBeans).
    • @Stateless(name="CollatzFacade",mappedName="ejb/CollatzFacade")
C:\_experiment\Collatz>c:\jdk1.6.0\bin\java -cp .;C:/opt/nbgf31b41/glassfish/lib/gf-client.jar;CollatzModel-jar.jar;CollatzGF-ejb.jar -Djava
.naming.factory.initial=com.sun.jndi.cosnaming.CNCtxFactory -Dorg.omg.CORBA.ORBInitialHost=127.0.0.1  -Dorg.omg.CORBA.ORBInitialPort=3700  o
rg.dataparallel.collatz.presentation.SEClient
Context for xps435 : javax.naming.InitialContext@122cdb6
javax.naming.NameNotFoundException [Root exception is org.omg.CosNaming.NamingContextPackage.NotFound: IDL:omg.org/CosNaming/NamingContext/N
otFound:1.0]
        at com.sun.jndi.cosnaming.ExceptionMapper.mapException(ExceptionMapper.java:44)
        at com.sun.jndi.cosnaming.CNCtx.callResolve(CNCtx.java:485)
        at com.sun.jndi.cosnaming.CNCtx.lookup(CNCtx.java:524)
        at com.sun.jndi.cosnaming.CNCtx.lookup(CNCtx.java:502)
        at javax.naming.InitialContext.lookup(InitialContext.java:392)

TC3:Remote Session Bean Lookup on GlassFish 3.1 from SE Client on Different JVM - Remote Machine

SE Client Logs


TC13: Remote Session Bean Lookup on WebLogic 10.3.4.0 from SE Client on Different JVM - Remote Machine

SE Client Logs

C:\_experiment\org.eclipse.persistence.example.distributed.CollatzSE\bin>c:\jdk1.6.0\bin\java -cp .;wlfullclient.jar org.eclipse.persistence.example.distributed.collatz.presentation.SEClient
Context for xps435 : javax.naming.InitialContext@1b1aa65
Remote Object: ClusterableRemoteRef(-6871653033103817620S:10.156.52.246:[7001,7001,-1,-1,-1,-1,-1]:base_domain:AdminServer [-6871653033103817620S:10.156.52.246:[7001,7001,-1,-1,-1,-1,-1]:base_domain:AdminServer/363])/363
Narrowed Session Bean: ClusterableRemoteRef(-6871653033103817620S:10.156.52.246:[7001,7001,-1,-1,-1,-1,-1]:base_domain:AdminServer [-6871653033103817620S:10.156.52.246:[7001,7001,-1,-1,-1,-1,-1]:base_domain:AdminServer/363])/363
UnitOfWork from: xps435 = org.dataparallel.collatz.business.UnitOfWork[id=5] 2147483649-2148532224
Range:       2147483649 to: 2148532224
M,1048575,0,2148041982,693      ,6694105047793216       ,9374   ,
P,1048575,0,2148398424,967      ,966616035460   ,15390  ,
UnitOfWork from: xps435 = org.dataparallel.collatz.business.UnitOfWork[id=8] 2148532225-2149580800

Results

EclipseLink 2.2 on GlassFish 3.1 within the same JVM in NetBeans 6.9.1

EclipseLink 2.2 on WebLogic 10.3.4.0 from either @Local or @Remote clients

  • Scenario: 2 remote clients on a SunFire server against a E8400 based server
[EL Fine]: 2011-02-14 13:58:45.562--ClientSession(4065049)--Connection(14012120)--Thread(Thread[[ACTIVE] ExecuteThread: '16' for queue: 'weblogic.kernel.Default (self-tuning)',5,Pooled Threads])--UPDATE PARAMETERS SET NEXTNUMBERTOSEARCH = ?, VERSION = ? WHERE ((ID = ?) AND (VERSION = ?))
	bind => [2868903938, 343, 4, 342]
[EL Fine]: 2011-02-14 13:58:45.562--ServerSession(8921344)--Connection(5230969)--Thread(Thread[[ACTIVE] ExecuteThread: '16' for queue: 'weblogic.kernel.Default (self-tuning)',5,Pooled Threads])--SELECT ID, CATEGORY, CORES, IDENT, PERFORMANCE, RANK, THREADS, VERSION, ACTIVEUNITOFWORK_ID FROM ACTIVEPROCESSOR WHERE (IDENT = ?)
	bind => [sunVM1]
_collatz: requestUnitOfWork(2868903938-2877292546) for processor org.eclipse.persistence.example.distributed.collatz.model.ActiveProcessor@89( id: 89)
	bind => [470, null, 2877292546, 2868903939, 1049, 7125885122794452160, 0, 1297708125562, 1, null, null, 89]
_collatz: Processing UOW: org.eclipse.persistence.example.distributed.collatz.model.UnitOfWork@469( id: 469) from processor: sunVM2
	bind => [2877292546, 344, 4, 343]
[EL Fine]: 2011-02-14 13:59:02.5--ServerSession(8921344)--Connection(8995870)--Thread(Thread[[ACTIVE] ExecuteThread: '19' for queue: 'weblogic.kernel.Default (self-tuning)',5,Pooled Threads])--SELECT ID, CATEGORY, CORES, IDENT, PERFORMANCE, RANK, THREADS, VERSION, ACTIVEUNITOFWORK_ID FROM ACTIVEPROCESSOR WHERE (IDENT = ?)
	bind => [sunVM2]
_collatz: requestUnitOfWork(2877292546-2885681154) for processor org.eclipse.persistence.example.distributed.collatz.model.ActiveProcessor@112( id: 112)
[EL Fine]: 2011-02-14 13:59:02.5--ClientSession(10474054)--Connection(31369678)--Thread(Thread[[ACTIVE] ExecuteThread: '19' for queue: 'weblogic.kernel.Default (self-tuning)',5,Pooled Threads])--INSERT INTO UNITOFWORK (ID, ENDTIMESTAMP, EXTENT, INITIAL, MAXPATH, MAXVALUE, RETRIES, STARTTIMESTAMP, VERSION, KNOWNMAX_ID, KNOWNPATH_ID, PROCESSOR_ID) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
	bind => [471, null, 2885681154, 2877292547, 1049, 7125885122794452160, 0, 1297708142500, 1, null, null, 112]


Statistics

JOPS per Watt

  • Since we have not implemented the core scalar integer processing in native C, SSE C or even GPU C the current "Java Operations (JOPS) per watt will suffice until we are able to state MIPS but not FLOPS.
    • The W1 NOC consisting of 5 machines (without monitor) consumes 6.9A of 120V power = 828W(RMS) or 828VA at 50% CPU.
    • The operations performed between 13:15 and 15:46 (151 min = 9060 sec) was (5662310402 - 1577058306 = 4,085,252,096).
    • This distributed result is 450,900 JOPS / 6.9A = 545 JOPS/Watt

Appendix

Enabling JPA 2.0 on WebLogic 10.3.4

  • Either follow the instructions on my other tutorial page, or let Eclipse 3.6 Helios change the order of the javax.persistence library and add the JPA 2.0 patch for container managed dependency injection to work.

Enable jpa2 in weblogic1034 from eclipse36.JPG

Enabling JSF 2.0 on WebLogic 10.3.4

  • Use Eclipse Helios 3.6 EE edition to enable the JSF 2.0 facet after you have installed Oracle WebLogic Server Tools as a server plugin to eclipse.
  • Note: JSF 2.0 managed beans either via @ManagedBean or via definition in faces-config.xml work fine when using the supplied JSF 2.0 library (2.0/1.0.0.0_2-0-2 in my case) in WebLogic Server 10.3.4.0. I have verified that the new .xhtml facelet based pattern using either annotations or XML works fine using an @EJB injected (in this case @Local) @Stateless session bean - that itself is injected with a JPA 2.0 container managed @PersistenceContext that handles persistence of the model.

Eclipse 36 helios for weblogic 1034 jsf2 facet.JPG

Enabling JAX-RS 1.1 on WebLogic 10.3.4

  • JAX-RS is recommended over traditional JAX-WS.
  • Similar to enabling JSF - we must enable the JAX-RS shared-library WAR on the server.
  • See C:\opt\wls1034r20110115\wlserver_10.3\common\deployable-libraries
    • Jersey Servlet Implementation = jersey-bundle-1.1.5.1.war
    • JAX-RS API = jsr311-api-1.1.1.war
    • Class Introspection = asm-3.1.jar (part of ?)
    • JSON processor = jackson-core-asl-1.1.1.war
    • JSON processor = jackson-jaxrs-1.1.1.war
    • JSON processor = jackson-mapper-asl-1.1.1.war
    • JSON Streaming = jettison-1.1.war
    • ATOM processing = rome-1.0.war
  • We don't have to do this manually, there is a script supplied to register the Jersey JAX-RS 1.1 RI on WebLogic 10.3.4
weblogic.Deployer -verbose -noexit -source C:\myinstall\wlserver_10.3\common\deployable-libraries\jersey-bundle-1.1.5.1.war -targets myserver -adminurl t3://localhost:7001 -user system -password ******** -deploy -library
weblogic.Deployer -verbose -noexit -source C:\myinstall\wlserver_10.3\common\deployable-libraries\jsr311-api-1.1.1.war -targets myserver -adminurl t3://localhost:7001 -user system -password ******** -deploy -library
   import javax.ws.rs.Get;
   import javax.ws.rs.Post;
   import javax.ws.rs.core UriInfo;
  • These should be..
   import javax.ws.rs.GET;
   import javax.ws.rs.POST;
   import javax.ws.rs.core.UriInfo;
  • In order to match their use as annotations later on.
  • I try to copy only directly from my Eclipse or NetBeans IDE - and not edit code directly on a blog - so this does not happen.

Deploying JSF 2.0 based EAR to alternate clean WebLogic 10.3.4 server

  • Scenario: I would like to deploy an EAR created from Eclipse 3.6 to another WebLogic 10.3.4 instance.
  • The problem is that the JSF library reference in weblogic.xml is not recognized on the new WebLogic server. I however have no issues running JSF on the first server managed by Eclipse.
Caused By: weblogic.management.DeploymentException: Error: Unresolved Webapp Library references for "ServletContext@64889218[app:_appsdir_org.eclipse.persistence.example.distributed.CollatzEAR_ear module:collatz path:/collatz spec-version:2.5]", defined in weblogic.xml [Extension-Name: jsf, Specification-Version: 2, exact-match: true]
	at weblogic.servlet.internal.WebAppServletContext.processWebAppLibraries(WebAppServletContext.java:2754)
  • This issue where a JSF 2.0 EAR created for an Eclipse 3.6 managed WebLogic 10.3.4.0 is solved by enabling the same JSF 2.0 library from Eclipse against the other remote WebLogic 10.3.4.0 where the EAR is exported to.
  • You should see the following logs on the 2nd WebLogic server console after the specific JSF 2.0 library has been enabled.
Feb 18, 2011 9:16:11 AM com.sun.faces.config.ConfigureListener contextInitialized
INFO: Initializing Mojarra 2.0.4 (FCS b05) for context '/collatz'
  • Notice that the version is 2.0.4 which is different that the default 1.2 version that the JSF based console uses.
Feb 18, 2011 9:22:19 AM com.sun.faces.config.ConfigureListener contextInitialized
INFO: Initializing Sun's JavaServer Faces implementation (1.2_03-b04-FCS) for context '/console'
Feb 18, 2011 9:22:19 AM com.sun.faces.config.ConfigureListener contextInitialized
INFO: Completed initializing Sun's JavaServer Faces implementation (1.2_03-b04-FCS) for context '/console'
  • An alternate solution to enabling JSF 2 and JAX-RS 1.1 on WebLogic Server 10.3.4 is to deploy the deployable shared libraries manually via the WebLogic console. Your deployed libraries should include the following.

Weblogic jsf jaxrs deployed libraries.JPG

Avoiding Obstructed SVN Web Project

  • For some reason SVN is reporting an obstructed web project whenever I modify the web project. I narrowed it down to Eclipse Helios causing the entries file at the root to be deleted on refresh.
C:\view_w36b\examples\org.eclipse.persistence.example.distributed.CollatzWeb\.svn\entries
  • The fix is to keep a copy of this file (as long as the root structure does not change) and replace it after a refresh in Eclipse - just before commit.

Derby ij command line SQL

  • To query the database outside eclipse (to avoid NullPointerExceptions from the data explorer plugin) use ij at
    • C:\opt\derby10530\bin\ij.bat
  • Connect with the following command
ij> connect 'jdbc:derby://127.0.0.1:1527/collatz;username=APP;password=APP';
ij> select * from activeprocessor;
ij> select p.id,p.ident,u.extent from activeprocessor p, unitofwork u where p.id=u.processor_id;

Using Thread Unsafe API in a Thread Safe Way

Use InheritableThreadLocal to make SimpleDateFormat Thread Safe

  • The SimpleDateFormat implementation of DateFormat is not thread safe by design. If you wish to use this API you have several architectures to choose from in your application.
    • Use sychronization in your implementation - not advisable since this will queue your requests
    • Use clones of the format for every thread or parse call
    • Use InheritableThreadLocal storage by using get/set to use an instance of SimpleDateFormat per thread - recommended for environments running in thread pools - like in EE application servers.
      • Here we use an InheritedThreadLocal map entry to store an instance of the DateFormat object for each thread. See p.45 section 3.3 Thread confinement of "Java Concurrency in Practice" by Brian Goetz. Also, do not set this field before starting this thread or the ThreadLocal map value will be cleared to initialValue()
      • However, we go further than the book because we also must override the childValue method of InheritableThreadLocal to handle the case where shared variables like the formatter are set before child threads are created. In that case we must clone the variable to maintain thread safety
    • Use a pool of SimpleDateFormat instances
  private static final InheritedThreadLocal<DateFormat> format = new InheritedThreadLocal<DateFormat>() {
    @Override
    public DateFormat initialValue() {
      // This default value will get replaced by non-default constructors
      return new SimpleDateFormat(DefaultFormat);
    }
    @Override
    // All child threads must clone the formatter and not share the parent formatter by default
    protected DateFormat childValue(DateFormat parentValue) {
        return (DateFormat) parentValue.clone();
    }
  };

SAXParser in JDOM SAXBuilder is not thread safe

  aBuilder = new SAXBuilder();
  aBuilder.setReuseParser(false);

TODO

  • Require @Servlet facade for non-EJB clients
  • Require @WebService facade for non-Java clients
  • Require JAX-RS facade for RESTful get/put/delete clients
  • Require @Singleton EJB 3.1 singleton @Schedule timer bean for asynchronous collating of the UnitOfWork records as well as passivation/unmarshalling of milestone records
  • Require JSF 2.0 @ManagedBean integration with EJB backend - done
  • According to http://jazoon.com/Portals/0/Content/slides/tu_a6_1630-1650_champenois.pdf the OEPE plugin supports EJB 3.1 @Singleton beans - however I was under the impression that this EJB 3.1 part and EJB Lite were not yet in WebLogic 10.3.4.0 - need to verify this by example
  • Get the JNDI name working for remote session bean lookup from an SE client in another JVM for GlassFish (this is working for WebLogic). Currently I can only get the case where the SE client is run in the same JVM as the server to work (where we use the no-arg constructor of InitialContext())
  • Reduce size of activeprocessor.identifier
  • add volumetrics tracking for mips
  • add derby ij section on sql scripting
  • move parameters.nextnumbertosearch to separate table to minimize optimistic locking exception handling
  • work units need 3 states (working, complete, incomplete - here we need a timer EJB to flag these)
  • KnownMax/Path are kind of redundant now that we associate a record instead of just a BigInteger with UOW and Parameters
  • The following SQL is not ordered in numeric order because the BigInteger attributes are converted to strings
    • select c.initial,c.pathlength,c.maximum from collatzrecord c where c.ispathrecord > 0 order by c.pathlength;
  • Add Java Web Start infrastructure for clients (and client updates)
  • Add a Swing SE GUI client app.
  • Add a brute force AJAX live client using the new Canvas element in HTML 5

References

Records

Orbit or Path Records

Record Start Orbit Maximum Start bits Maximum bits - Found by
88 881,715,740,415 1334 Michael O'Brien

20110306

87 674,190,078,379 1331 Michael O'Brien

20110303

86 568,847,878,633 1324 Michael O'Brien

20110301

85 426,635,908,975 1320 Michael O'Brien

20110227

8 27 110 9232 Michael O'Brien
ij> select * from collatzRecord where pathlength ='1320';
ID|INITIAL|MAXIMUM|PATHLENGTH|VERSION|UNITOFWORK_ID
203703|426635908975|2662567439048656|1320|1|203696
213204|446559217279|39533276910778060381072|1320|213198
2 rows selected

Max Records

Record Start Path Maximum Start bits Max bits Max/start Found by
88 1,980,976,057,694,848,447 1475 64,024,667,322,193,133,530,165,877,294,264,738,020 61 125 note:

125 > 61*2

Tomás Oliveira e Silva

max is at step 690

87 1,038,743,969,413,717,663 319,391,343,969,356,241,864,419,199,325,107,352 60 118 Tomás Oliveira e Silva, Eric Roosendaal
-- 871,673,828,43 400,558,740,821,250,122,033,728 40 79 Leavens & Vermeulen, Michael O'Brien

20110306

59 567,839,862,631 100,540,173,225,585,986,235,988 40 77 Leavens & Vermeulen, Michael O'Brien

20110301

58 446,559,217,279 39,533,276,910,778,060,381,072 39 76 Leavens & Vermeulen, Michael O'Brien

20110226

44 1,410,123,943 7,125,885,122,794,452,160 31 63
5 27 110 9232 Leavens & Vermeulen, Michael O'Brien

Progress

  • As of 20110228:1100EDT after 9.75 days of server up time since 20110218:1620EDT using an average of 9 distributed JVM's on 7 machines running in parallel using an unoptimized version of the distributed collatz software - we are at.
Current Search Number
534,136,224,795
Maximum Value #58
39,533,276,910,778,060,381,072
Path Path
1320
search packet interval
2,097,151 = 2 ^21

Design Issues

Back to the top