java client 0.9.1

Russo, Richard russor@msoe.edu
Sun, 12 Oct 2003 19:13:20 -0700


--Message-Boundary-8052
Content-type: text/plain; charset=US-ASCII
Content-transfer-encoding: 7BIT
Content-description: Mail message body

I've attached what I think is a useful patch to the java client.  I'm 
not sure what the best way of generating patches is, so that's why 
I'm not sure if it's useful.

I did cvs diff -uN... if there's a preferred way of doing it, I can 
do that instead.

This patch makes some stuff prettier, and adds the single client, and 
single key get optimizations that were added to the perl client 
recently.  The network I/O was already pretty much optimized, but I 
changed it from a looping read (like the perl client has) to a 
blocking read, to reduce the code size a tad.

Thanks,

toast



--Message-Boundary-8052
Content-type: text/plain; charset=US-ASCII
Content-transfer-encoding: 7BIT
Content-description: Text from file 'patch-0.9.1'

Index: CHANGELOG.txt
===================================================================
RCS file: /home/cvspub/wcmtools/memcached/api/java/CHANGELOG.txt,v
retrieving revision 1.1
diff -u -r1.1 CHANGELOG.txt
--- CHANGELOG.txt	5 Oct 2003 21:48:05 -0000	1.1
+++ CHANGELOG.txt	13 Oct 2003 01:45:27 -0000
@@ -1,3 +1,20 @@
+Version 0.9.1 - 12 Oct 2003
+	-- Altered the SockIO helper class, so it no longer allows accessing
+		the streams it contains directly, instead it has methods
+		with identical signatures to the methods that were called
+		on the streams... This makes the client code prettier.
+	-- Changed looped non blocking read to blocking read, for getting
+		items from the server. This probably reduces CPU usage in
+		cases where the retrieval would block, and cleans up the
+		code a bit.  We're blocking on retrieval anyhow.
+	-- Made get() not call get_multi(), and added single socket
+		optimization. This parallels recent changes to the perl
+		client
+	-- Changed a few for loops to use iterators instead, since it's
+		probably marginally more efficient, and it's probably
+		better coding practice.
+	-- Actually spell checked. :)
+
 Version 0.9.0 - 29 Sep 2003
 	-- Renumbered to reflect that it's not been realworld tested
 	-- changed package to danga.com.MemCached (thanks)
Index: TODO.txt
===================================================================
RCS file: /home/cvspub/wcmtools/memcached/api/java/TODO.txt,v
retrieving revision 1.1
diff -u -r1.1 TODO.txt
--- TODO.txt	5 Oct 2003 21:48:05 -0000	1.1
+++ TODO.txt	13 Oct 2003 01:45:27 -0000
@@ -2,7 +2,7 @@
 
 Investigate threading issues (what needs to be synchronized)
 
-Investigate 3rd party jvm incompatability
+Investigate 3rd party jvm incompatibility
 
 Non deprecated stream input
 
Index: com/danga/MemCached/MemCachedClient.java
===================================================================
RCS file: /home/cvspub/wcmtools/memcached/api/java/com/danga/MemCached/MemCachedClient.java,v
retrieving revision 1.1
diff -u -r1.1 MemCachedClient.java
--- com/danga/MemCached/MemCachedClient.java	5 Oct 2003 21:48:05 -0000	1.1
+++ com/danga/MemCached/MemCachedClient.java	13 Oct 2003 01:45:28 -0000
@@ -14,7 +14,7 @@
  * This is free software. IT COMES WITHOUT WARRANTY OF ANY KIND.
  *
  * @author  Richard 'toast' Russo <russor@msoe.edu>
- * @version 0.9.0
+ * @version 0.9.1
  */
 
 
@@ -29,10 +29,10 @@
 
 
 /** This is a Java client for the memcached server available from
- *  <a href="http:/www.danga.com/memcached/">http://www.danga.com/mecmched/</a>.*/
+ *  <a href="http:/www.danga.com/memcached/">http://www.danga.com/memcached/</a>.*/
 public class MemCachedClient {
     
-    final int F_COMPRESSED = 2; //same as perl flag... but not implemented
+    final int F_COMPRESSED = 2;
     final int F_SERIALIZED = 8;
     //using 8 (1 << 3) so other clients don't try to unpickle/unstore/whatever
     //things that are serialized... I don't think they'd like it. :)
@@ -44,6 +44,7 @@
     boolean debug = false;
     boolean forceserial = false;
     
+    String singlesock = null;
     double compress_savings = 0.20;
     boolean compress_enable = true;
     int compress_threshold = 1024; // FIXME: is this a reasonable default??
@@ -51,7 +52,7 @@
     /** Creates a new instance of MemCachedClient.
      *
      * By default, compression is enabled, with a threshold of 1024, and required
-     * savings of 0.2; debug is disabled; forced serialization is disabld; and the
+     * savings of 0.2; debug is disabled; forced serialization is disabled; and the
      * server list is empty.
      */
     public MemCachedClient() {
@@ -86,8 +87,8 @@
         compress_enable = b;
     }
     
-    /** Sets the required lenght for data to be considered for compression. If the
-     * lenght of the data to be stored is not equal or larger than this value, it will
+    /** Sets the required length for data to be considered for compression. If the
+     * length of the data to be stored is not equal or larger than this value, it will
      * not be compressed.
      *
      * This defaults to 1024.
@@ -112,7 +113,7 @@
      * system.  If you're not having problems, this is probably not useful for you.
      * @param b <CODE>true</CODE>, if debugging messages are desired, <CODE>false</CODE> if debugging messages are not
      * desired
-     */    
+     */
     public void set_debug(boolean b) {
         debug = b;
     }
@@ -147,6 +148,12 @@
         buckets = new ArrayList();
         host_dead = new HashMap();
         sockets = new HashMap();
+        if (serverlist.length == 1) {
+            singlesock = serverlist[0];
+        } else {
+            singlesock = null;
+        }
+        
         
         for (int i = 0; i < serverlist.length; ++i) {
             if (weightlist != null && weightlist.length >i) {
@@ -160,6 +167,9 @@
     }
     
     private SockIO get_sock(Object key) {
+        if (singlesock != null) {
+            return sock_to_host(singlesock);
+        }
         if (key.getClass() == Integer.class) {
             return get_sock(((Integer)key).intValue());
         } else {
@@ -169,10 +179,16 @@
     
     
     private SockIO get_sock(String key) {
+        if (singlesock != null) {
+            return sock_to_host(singlesock);
+        }
         return get_sock(hashfunc(key));
     }
     
     private SockIO get_sock(int key) {
+        if (singlesock != null) {
+            return sock_to_host(singlesock);
+        }
         if ( buckets.size() == 0) {
             return null;
         }
@@ -255,13 +271,13 @@
     
     /** Deletes a key from the server; only the key is specified.
      * @param key the key to be removed
-     * @return true, if the data was deleted succesfully
+     * @return true, if the data was deleted successfully
      */
     public boolean delete(String key) {
         return delete(key,null, null);
     }
     /** Deletes a key from the server; the key, and a hash value are specified.
-     * @return true, if the data was deleted succesfully
+     * @return true, if the data was deleted successfully
      * @param hash used to determine which server is responsible for the specified key
      * @param key the key to be removed
      */
@@ -269,7 +285,7 @@
         return delete(key,null,new Integer(hash));
     }
     /** Deletes a key from the server; the key, and a hash value are specified.
-     * @return true, if the data was deleted succesfully
+     * @return true, if the data was deleted successfully
      * @param hash used to determine which server is responsible for the specified key
      * @param key the key to be removed
      */
@@ -277,13 +293,13 @@
         return delete(key, null, hash);
     }
     /** Deletes a key from the server; the key, and a delete time are specified. The item
-     *  is immediately made non retreivable, however {@link #add(String, Object) add} and
+     *  is immediately made non retrievable, however {@link #add(String, Object) add} and
      *  {@link #replace(String, Object) replace} will fail when used with the same key will
      *  fail, until the server reaches the specified time. However,
      *  {@link #set(String, Object) set} will succeed, and the new value will not
      *  be deleted.
      *
-     * @return true, if the data was deleted succesfully
+     * @return true, if the data was deleted successfully
      * @param expiry when to expire the record
      * @param key the key to be removed
      */
@@ -292,13 +308,13 @@
         return delete(key,expiry,null);
     }
     /** Deletes a key from the server; the key, a delete time are specified, and a hash value
-     *  are specified. The item is immediately made non retreivable, however
+     *  are specified. The item is immediately made non retrievable, however
      *  {@link #add(String, Object) add} and {@link #replace(String, Object) replace}
      *  will fail when used with the same key will fail, until the server reaches the
      *  specified time. However, {@link #set(String, Object) set} will succeed,
      *  and the new value will not be deleted.
      *
-     * @return true, if the data was deleted succesfully
+     * @return true, if the data was deleted successfully
      * @param expiry when to expire the record.
      * @param hash used to determine which server is responsible for the specified key
      * @param key the key to be removed
@@ -308,13 +324,13 @@
     }
     
     /** Deletes a key from the server; the key, a delete time are specified, and a hash value
-     *  are specified. The item is immediately made non retreivable, however
+     *  are specified. The item is immediately made non retrievable, however
      *  {@link #add(String, Object) add} and {@link #replace(String, Object) replace}
      *  will fail when used with the same key will fail, until the server reaches the
      *  specified time. However, {@link #set(String, Object) set} will succeed,
      *  and the new value will not be deleted.
      *
-     * @return true, if the data was deleted succesfully
+     * @return true, if the data was deleted successfully
      * @param expiry when to expire the record.
      * @param hash used to determine which server is responsible for the specified key
      * @param key the key to be removed
@@ -337,10 +353,10 @@
         command = command + "\r\n";
         
         try {
-            sock.out().writeBytes(command);
-            sock.out().flush();
+            sock.writeBytes(command);
+            sock.flush();
             
-            command = sock.in().readLine();
+            command = sock.readLine();
             if (command.equals("DELETED")) {
                 return true;
             }
@@ -746,19 +762,18 @@
         try {
             String cmd = cmdname + " " + key + " " + flags + " " +
             expiry.getTime() / 1000 + " " + val.length + "\r\n";
-            sock.out().writeBytes(cmd);
-            sock.out().write(val);
-            sock.out().writeBytes("\r\n");
-            sock.out().flush();
+            sock.writeBytes(cmd);
+            sock.write(val);
+            sock.writeBytes("\r\n");
+            sock.flush();
             
-            String tmp = sock.in().readLine();
-            if (tmp.equals("STORED")) {
-                if (debug) {
-                    System.out.println("MemCache: " + cmdname + " " + key + " = " + val);
-                }
+            String line = sock.readLine();
+            if (debug) {
+                System.out.println("MemCache: " + cmdname + " " + key +
+                " = " + val + "(" + line + ")");
+            }
+            if (line.equals("STORED")) {
                 return true;
-            } else {
-                System.out.println("MemCache:" + cmd + tmp);
             }
             
         } catch (IOException e) {
@@ -968,9 +983,9 @@
         }
         
         try {
-            sock.out().writeBytes(cmdname + " " + key + " " + inc + "\r\n");
-            sock.out().flush();
-            String tmp = sock.in().readLine();
+            sock.writeBytes(cmdname + " " + key + " " + inc + "\r\n");
+            sock.flush();
+            String tmp = sock.readLine();
             return Long.decode(tmp).longValue();
         } catch (IOException e) {
             sock.close();
@@ -986,7 +1001,7 @@
      *  be decompressed or serialized, as appropriate. (Inclusive or)
      *
      *  Non-serialized data will be returned as a string, so explicit conversion to
-     *  numeric types will be necessisary, if desired
+     *  numeric types will be necessary, if desired
      *
      * @param key key where data is stored
      * @return the object that was previously stored, or null if it was not previously stored
@@ -1001,7 +1016,7 @@
      *  be decompressed or serialized, as appropriate. (Inclusive or)
      *
      *  Non-serialized data will be returned as a string, so explicit conversion to
-     *  numeric types will be necessisary, if desired
+     *  numeric types will be necessary, if desired
      *
      * @param key key where data is stored
      * @param hash used to determine which server is responsible for the specified key
@@ -1017,23 +1032,45 @@
      *  be decompressed or serialized, as appropriate. (Inclusive or)
      *
      *  Non-serialized data will be returned as a string, so explicit conversion to
-     *  numeric types will be necessisary, if desired
+     *  numeric types will be necessary, if desired
      *
      * @param key key where data is stored
      * @param hash used to determine which server is responsible for the specified key
      * @return the object that was previously stored, or null if it was not previously stored
      */
     public Object get(String key, Object hash) {
-        String[] k = { key };
-        Object[] h = { hash };
-        return get_multi(k,h).get(key);
+        SockIO sock;
+        if (hash != null) {
+            sock = get_sock(hash);
+        } else {
+            sock = get_sock(key);
+        }
+        
+        if (sock == null) {
+            return null;
+        }
+        try {
+            sock.writeBytes("get " + key + "\r\n");
+        } catch (IOException e) {
+            sock.close();
+        }
+        HashMap hm = new HashMap();
+        load_items(sock, hm);
+        if (debug) {
+            Iterator i = hm.entrySet().iterator();
+            while (i.hasNext()) {
+                Entry e = (Entry)i.next();
+                System.out.println("MemCache: got " + e.getKey() + " = " + e.getValue());
+            }
+        }
+        return hm.get(key);
     }
     
     
     /** Retrieve multiple keys from the memcache.
      *
      *  This is recommended over repeated calls to {@link #get(String) get()}, since it
-     *  is more efficent.
+     *  is more efficient.
      * @return a hashmap with entries for each key is found by the server,
      *      keys that are not found are not entered into the hashmap, but attempting to
      *      retrieve them from the hashmap gives you null.
@@ -1043,10 +1080,10 @@
         return get_multi(keys, null);
     }
     
-    /** Retrieve multiple keys from the memcache. 
+    /** Retrieve multiple keys from the memcache.
      *
      *  This is recommended over repeated calls to {@link #get(String) get()}, since it
-     *  is more efficent.
+     *  is more efficient.
      * @param keys keys to retrieve
      * @param hashes hash values used to determine which server to use for each key;
      *      if a hash is not provided for a key (either because there are more keys
@@ -1077,12 +1114,15 @@
         
         // Pass 1: send out requests
         
-        for (int i = 0; i < socks.size(); ++i) {
+        ArrayList gather = new ArrayList();
+        Iterator i = socks.iterator();
+        while (i.hasNext()) {
             SockIO sock =null;
             try {
-                sock = (SockIO) socks.get(i);
-                sock.out().writeBytes("get" + (StringBuffer)sock_keys.get(sock) + "\r\n");
-                sock.out().flush();
+                sock = (SockIO) i.next();
+                sock.writeBytes("get" + (StringBuffer)sock_keys.get(sock) + "\r\n");
+                sock.flush();
+                gather.add(sock);
             } catch (IOException e) {
                 sock.close();
             }
@@ -1090,12 +1130,13 @@
         
         HashMap ret = new HashMap();
         // Pass 2: get results
-        for (int i = 0; i < socks.size(); ++i) {
-            load_items((SockIO) socks.get(i), ret);
+        i = gather.iterator();
+        while (i.hasNext()) {
+            load_items((SockIO) i.next(), ret);
         }
         
         if (debug) {
-            Iterator i = ret.entrySet().iterator();
+            i = ret.entrySet().iterator();
             while (i.hasNext()) {
                 Entry e = (Entry)i.next();
                 System.out.println("MemCache: got " + e.getKey() + " = " + e.getValue());
@@ -1107,7 +1148,7 @@
     private void load_items(SockIO sock, HashMap hm) {
         try {
             while (true) {
-                String line = sock.in().readLine();
+                String line = sock.readLine();
                 if (line == null) {
                     return;
                 } else if (line.startsWith("VALUE")) {
@@ -1122,16 +1163,9 @@
                     st.nextToken();
                     int length = new Integer(st.sval).intValue();
                     byte[] buf = new byte[length];
-                    int read = 0;
-                    while (read < length) {
-                        int tmp = sock.in().read(buf, read, length - read);
-                        if (tmp == -1) {
-                            return;
-                        }
-                        read+= tmp;
-                    }
+                    sock.readFully(buf); // blocking read
                     
-                    sock.in().readLine(); // clear out \r\n that should be left
+                    sock.readLine(); // clear out \r\n that should be left
                     // check for compression
                     Object o;
                     
Index: com/danga/MemCached/SockIO.java
===================================================================
RCS file: /home/cvspub/wcmtools/memcached/api/java/com/danga/MemCached/SockIO.java,v
retrieving revision 1.1
diff -u -r1.1 SockIO.java
--- com/danga/MemCached/SockIO.java	5 Oct 2003 21:48:05 -0000	1.1
+++ com/danga/MemCached/SockIO.java	13 Oct 2003 01:45:28 -0000
@@ -11,11 +11,11 @@
  * This is free software. IT COMES WITHOUT WARRANTY OF ANY KIND.
  *
  * @author  Richard 'toast' Russo <russor@msoe.edu>
- * @version 0.9.0
+ * @version 0.9.1
  */
 
 
-package com.danga.MemCached; 
+package com.danga.MemCached;
 
 
 import java.util.*;
@@ -40,20 +40,33 @@
     public void close() {
         closed = true;
         try {
-        in.close();
-        out.close();
-        sock.close();
+            in.close();
+            out.close();
+            sock.close();
         } catch (IOException e) {
         }
         
     }
-    public DataInputStream in() {
-        return in;
-    }
-    public DataOutputStream out() {
-        return out;
-    }
     public boolean isConnected() {
         return (closed && sock.isConnected());
     }
+    
+    public void readFully(byte[] b) throws IOException {
+        in.readFully(b);
+    }
+    
+    public String readLine() throws IOException {
+        return in.readLine();
+    }
+    
+    public void writeBytes(String s) throws IOException {
+        out.writeBytes(s);
+    }
+    public void flush() throws IOException {
+        out.flush();
+    }
+    public void write(byte[] b) throws IOException {
+        out.write(b);
+    }
+    
 }

--Message-Boundary-8052--