JPPF
 Home   About   Download   Documentation   Forums 

Extended Class Loading sample

Quick navigation

1. What does the sample do? 5. Source files
2. Description of the problem 6. Features demonstrated
3. Description of the solution 7. Help and support
4. How do I run it? 

What does the sample do?

This sample uses the JPPF class loading extensions to automate the management of a repository of application libraries at runtime, for all the nodes in the grid.

Description of the problem to solve

Some applications require a large number of internal or external libraries to run. When executed in a JPPF grid, they may incur a significant startup time, due to the loading of a very large number of classes across the network, which is the way JPPF works by default. Futhermore, this startup overhead may occur every time a change occurs, not only in the application's code but also in any of the libraries it relies on.

A solution to the startup time issue is to deploy the libraries locally on each node. However, this causes a management and deployment overhead, when one or more of the libraries is added, updated or removed. When the number of nodes in the grid is large, the overhead of managing the libraries can be prohibitive.

Description of the solution

In this sample, we implement a mechanism that will automatically detect libraries that were added, updated or removed on the client side. This information is communicated to the nodes by annotating a JPPF job with metadata describing the changes in the repository of libraries. There will be two kinds of repositories: one that is on the client side and maintained by the users, the other one local to each node, which will be automatically updated by comparing vith the job metadata.

To be able to determine if a library was updated, each library is associated with a signature, such as an MD5 of SHA-256 signature. The association between library files and signatures is maintained and stored in an index file in the repository, by default called "index.txt". Additionally, each library file on the node side will be suffixed with its corresponding signature in hexadecimal representation. For instance, a file "MyLibrary.jar" will be named "MyLibrary-B48D97023F956A93282DB8B12C47443B.jar". This "trick" is necessary since the files are added to the node's classpath, and the JVM may be holding a lock on them (especially on Windows systems), thus preventing us from overwriting them. For the same reason, it may not be possible to immediately delete a file that was removed from the repository. Thus, the files to delete are stored in another text file "toDelete.txt", so they can be deleted the next time the node is restarted.

On the node, we use a node life cycle listener to perform the maintenance operations on the repository:

  • On "node starting" events, the node deletes the files listed in the "toDelete.txt" file, and adds the libraries listed in the "index.txt" file to the classpath
  • On "node ending" events, the node simply stores the latest state of the "toDelete.txt" file
  • On "job starting" events, the node reads the metadata associated with the jobs, and compares them with the content of the repository. It will then download the new or updated libraries from the client and store them in its repository, updating the repository's index at the same time.
    It will also add the new libraries to its classpath. It doesn't make sense to do that with updated libraries, because the old version is already in the classpath. Thus, old versions are scheduled for deletion at the next startup, by adding them to the "toDelete.txt" file.
    At this point, if there are any changes to the repository, the node will save both the index file and the "toDelete" file.
    Finally, the node will check for a "node restart" flag in the job metadata. If the flag is true, then the job is cancelled, causing it to be requeued on the server, and the node is immediately restarted so the changes to the classpath can be taken into account.
  • On "job ending" events, there is no specific processing taking place.

On the client side, we have a mechanism that scans the repository, and compares the scan result with the content of the index file, to determine which libraries were added, updated or removed. This information is then added to a job's metadata, and the job is submitted to the JPPF grid. When the job is dispatched to a node, the node will then be able to process the metadata to update its own repository and classpath.

The repository is a simple flat folder that contains jar files. This folder is added to the JPPF client's classpath, but not the jar files it contains. Using this structure, it is very easy to update the repository: simply drop a jar file into the repository folder, or remove one from the folder, and all the rest is automated.

How do I run it?

Before running this sample, you need to install a JPPF server and at least one node.
For information on how to set up a node and server, please refer to the JPPF documentation.
Once you have installed a server and node, perform the following steps:
  1. open a command prompt or shell console in JPPF-x.y-samples-pack/ExtendedClassLoading
  2. build the sample: type "ant jar" or simply "ant"; this will create a 3 jar files:
    • NodeListener.jar in this sample's root folder. This is our node life cycle listener implementation
    • ClientLib1.jar and ClientLib2.jar in the "dynamicLibs" folder (this is the client's repository). These are here for demonstration purposes. Each of these libraries contains a single class used by the JPPF task in the submitted job. When running this sample the first time, these classes will initially not be in the classpath of either the client or the node. However, our repository management mechanism will automatically download these libraries to the node, so the task can be executed without error.
  3. copy "NodeListener.jar" in the "lib" folder of the JPPF driver installation, to add it to the driver's classpath. This will cause the nodes to download its classes from the server.
  4. start the server and the node
  5. from the command prompt previously opened, run the sample by typing "ant run"
  6. in the client's console, you should see the following messages displayed (ignoring the driver connection messages):
    found 2 new or updated libraries
      - NEW   : ClientLib2.jar, signature = 651DC2B98EAEFD159755786BDB5DF316
      - NEW   : ClientLib1.jar, signature = 0010A74AEF47E32B138CA842893679DD
    there are no deleted libraries
    restart node flag set to false
    ...
    Result: Successful execution
  7. in the node's console, you should see the following messages displayed:
    processing metadata for job 'Extended Class Loading'
      found 2 libraries to update:
      - NEW   : ClientLib2.jar, signature = 651DC2B98EAEFD159755786BDB5DF316
      - NEW   : ClientLib1.jar, signature = 0010A74AEF47E32B138CA842893679DD
      no library to remove
    Hello from class 1 loaded from the client
    Hello from class 2 loaded from the client
  8. Now remove the file "ClientLib1.jar" from the "dynamicLibs" folder
  9. Run the sample again, but this time asking that the node be restarted:
    type the command "ant -Drestart.node=true run"
  10. This time you will see the following in the client's console:
    there are no new or updated libraries
    found 1 deleted library
      - ClientLib1.jar
    restart node flag set to true
    Got exception: org.jppf.JPPFException:
      java.lang.NoClassDefFoundError: org/jppf/.../MyClientDynamicClass1
    ... stack trace ...
  11. and in the node:
    processing metadata for job 'Extended Class Loading'
      no library updates found
      found 1 libraries to delete:
      - ClientLib1.jar
    canceling the job
    *** restarting this node ***
    node process id: 1448
    [ ... connection to the server ...]
    found 1 library in the store:
      added ClientLib2.jar to the classpath
    Node successfully initialized
    processing metadata for job 'Extended Class Loading'
      no library updates found
  12. What happened here? From the messages on the node and client side, we can see the following:
    • upon receiving the job, the node detects that "ClientLib1.jar" is to be deleted from its repository
    • after performing this update, the job is cancelled and the node restarted as requested
    • upon starting again, the node adds the libraries found in its repository to the classpath: only ClientLib2.jar is left
    • We don't see any message from the task executing, because an exception occurred
    • the client reports the exception, caused by the fact that "MyClientDynamicClass1" was not in the classpath anymore
  13. Now, you can continue experimenting with the content of the client's repository. You may re-create or update the ClientLib1.jar and ClientLib2.jar libraries by running the corresponding ant scripts: "ant jar.1" or "ant jar.2". You may also play with the "restart node" flag to see how the changes take effect. You might also want to try adding any other jar file to the client's repository.

Commented source files

  • LibraryManager.java: This is the utility class which performs the repository management operations
  • NodeListener.java: our node life cycle listener implementation, which uses the LibraryManager
  • MyRunner.java: the JPPF client application, which uses the LibraryManager
  • MyTask.java: a sample JPPF task which explicitely loads classes dynamically

What features of JPPF are demonstrated?

I have additional questions and comments, where can I go?

If you need more insight into the code of this demo, you can consult the Java source files located in the ExtendedClassLoading/src folder.

In addition, There are 2 privileged places you can go to:

Support This Project Copyright © 2005-2011 JPPF.org Powered by Parallel Matters  Get JPPF at SourceForge.net. Fast, secure and Free Open Source software downloads