Index: src/components/org/apache/jmeter/config/CSVDataSet.java
===================================================================
--- .	(revision 630226)
+++ .	(working copy)
@@ -24,6 +24,8 @@
 import org.apache.jmeter.engine.event.LoopIterationEvent;
 import org.apache.jmeter.engine.event.LoopIterationListener;
 import org.apache.jmeter.services.FileServer;
+import org.apache.jmeter.services.FileServerAbstract;
+import org.apache.jmeter.services.FileServerNonThreading;
 import org.apache.jmeter.testbeans.TestBean;
 import org.apache.jmeter.threads.JMeterVariables;
 import org.apache.jmeter.util.JMeterUtils;
@@ -55,9 +57,17 @@
     private transient boolean recycle = true;
     
     private transient boolean stopThread = false;
+    
+    private transient boolean independent = false;
+    
+    private transient int maxLoopCount = 0;
+    
+    private FileServerNonThreading server;
+    
+    private int curLoopCount = 1;
 
     transient private String[] vars;
-
+    
     private Object readResolve(){
         recycle = true;
         return this;
@@ -62,6 +72,7 @@
         recycle = true;
         return this;
     }
+    
 	/*
 	 * (non-Javadoc)
 	 * 
@@ -68,7 +79,15 @@
 	 * @see org.apache.jmeter.engine.event.LoopIterationListener#iterationStart(org.apache.jmeter.engine.event.LoopIterationEvent)
 	 */
 	public void iterationStart(LoopIterationEvent iterEvent) {
-		FileServer server = FileServer.getFileServer();
+		FileServerAbstract server;
+		if (!getIndependent()){
+			server  = FileServer.getFileServer();
+		}else{
+			if (this.server == null)
+				this.server = new FileServerNonThreading();
+			server = this.server;		
+		}
+	
 		String _fileName = getFilename();
 		if (vars == null) {
 			server.reserveFile(_fileName, getFileEncoding());
@@ -79,7 +98,32 @@
 			if (delim.equals("\\t")) // $NON-NLS-1$
 				delim = "\t";// Make it easier to enter a Tab // $NON-NLS-1$
             JMeterVariables threadVars = this.getThreadContext().getVariables();
-			String line = server.readLine(_fileName,getRecycle());
+            
+            String line;
+            // Check recycle and maxLoopCount
+            if (getRecycle()){
+            	
+            	if (this.maxLoopCount > 0){
+            		// Read without recycle
+            		line = server.readLine(_fileName, false);
+            		
+            		// Check for EOF
+            		if (line == null && this.curLoopCount < this.maxLoopCount){
+            			// Recycle
+            			line = server.readLine(_fileName, true);
+            			this.curLoopCount++;
+            		}
+            		
+            	}else{
+            		// Just read with recycle
+            		line = server.readLine(_fileName,true);
+            	}
+            	
+            }else{
+            	// don't recycle
+            	line = server.readLine(_fileName,false);
+            }
+			
             if (line!=null) {// i.e. not EOF
                 String[] lineValues = JOrphanUtils.split(line, delim,false);
     			for (int a = 0; a < vars.length && a < lineValues.length; a++) {
@@ -105,6 +149,21 @@
 	public String getFilename() {
 		return filename;
 	}
+	
+	/**
+	 * @return Returns if it should use the global file server or not.
+	 */
+	public boolean getIndependent(){
+		return this.independent;
+	}
+	
+	/**
+	 * @param shareServer
+	 * 			  Set to true if each thread should have its own file server.
+	 */
+	public void setIndependent(boolean shareServer){
+		this.independent = shareServer;
+	}
 
 	/**
 	 * @param filename
@@ -167,5 +226,23 @@
     public void setStopThread(boolean value) {
         this.stopThread = value;
     }
-
+    
+    /**
+     * @param value
+     *             Sets how often it loops through the whole file
+     */
+    public void setMaxLoopCount(String value){
+    	// Check if numeric
+    	try{
+    		this.maxLoopCount = Integer.parseInt(value);
+    	}catch (Exception e){
+    		this.maxLoopCount = 0;
+    	}
+    }
+    
+    public String getMaxLoopCount(){
+    	return Integer.toString(this.maxLoopCount);
+    }
+    
+    
 }
Index: src/components/org/apache/jmeter/config/CSVDataSetBeanInfo.java
===================================================================
--- .	(revision 630226)
+++ .	(working copy)
@@ -35,6 +35,10 @@
     private static final String DELIMITER = "delimiter";             //$NON-NLS-1$
     private static final String RECYCLE = "recycle";                 //$NON-NLS-1$
     private static final String STOPTHREAD = "stopThread";           //$NON-NLS-1$
+    private static final String INDEPENDENT = "independent"; //$NON-NLS-1$    
+    private static final String MAXLOOPCOUNT = "maxLoopCount"; //$NON-NLS-1$
+    
+    
 
 	public CSVDataSetBeanInfo() {
 		super(CSVDataSet.class);
@@ -39,7 +43,8 @@
 	public CSVDataSetBeanInfo() {
 		super(CSVDataSet.class);
 		createPropertyGroup("csv_data",             //$NON-NLS-1$
-                new String[] { FILENAME, FILE_ENCODING, VARIABLE_NAMES, DELIMITER, RECYCLE, STOPTHREAD });
+                new String[] { FILENAME, FILE_ENCODING, VARIABLE_NAMES, 
+					DELIMITER, RECYCLE, STOPTHREAD, INDEPENDENT, MAXLOOPCOUNT });
         
 		PropertyDescriptor p = property(FILENAME);
 		p.setValue(NOT_UNDEFINED, Boolean.TRUE);
@@ -70,5 +75,15 @@
         p.setValue(NOT_UNDEFINED, Boolean.TRUE);
         p.setValue(DEFAULT, Boolean.FALSE);
         p.setValue(NOT_EXPRESSION, Boolean.TRUE);
-	}
+
+        p = property(INDEPENDENT);
+        p.setValue(NOT_UNDEFINED, Boolean.TRUE);
+        p.setValue(DEFAULT, Boolean.FALSE);
+        p.setValue(NOT_EXPRESSION, Boolean.TRUE);
+
+        p = property(MAXLOOPCOUNT);
+        p.setValue(NOT_UNDEFINED, Boolean.TRUE);
+        p.setValue(DEFAULT, "0");
+        p.setValue(NOT_EXPRESSION, Boolean.TRUE);
+}
 }
Index: src/components/org/apache/jmeter/config/CSVDataSetResources.properties
===================================================================
--- .	(revision 630226)
+++ .	(working copy)
@@ -11,4 +11,8 @@
 recycle.displayName=Recycle on EOF ?
 recycle.shortDescription=Should the file be re-read from the start on reaching EOF ?
 stopThread.displayName=Stop thread on EOF ?
-stopThread.shortDescription=Should the thread be stopped on reaching EOF (if Recycle is false) ?
\ No newline at end of file
+stopThread.shortDescription=Should the thread be stopped on reaching EOF (if Recycle is false) ?
+independent.displayName=Independent CSV Data Source?
+independent.shortDescription=Should each thread has its own independent CSV Data Source?
+maxLoopCount.displayName=Maximum loop count
+maxLoopCount.shortDescription=Sets the number of loops through the file (if Recycle is true)
\ No newline at end of file
Index: src/core/org/apache/jmeter/services/FileServer.java
===================================================================
--- .	(revision 630226)
+++ .	(working copy)
@@ -54,7 +54,7 @@
  * server has all support files in a relative-same location) and to package up
  * test plans to execute on unknown boxes that only have Java installed.
  */
-public class FileServer {
+public class FileServer extends FileServerAbstract {
 	private static final Logger log = LoggingManager.getLoggerForClass();
 
     private static final String DEFAULT_BASE = JMeterUtils.getProperty("user.dir");
Index: src/core/org/apache/jmeter/services/FileServerAbstract.java
===================================================================
--- src/core/org/apache/jmeter/services/FileServerAbstract.java	(revision 0)
+++ src/core/org/apache/jmeter/services/FileServerAbstract.java	(revision 0)
@@ -0,0 +1,31 @@
+package org.apache.jmeter.services;
+
+import java.io.File;
+import java.io.IOException;
+
+public abstract class FileServerAbstract {
+	
+	public abstract void resetBase() throws IOException;
+	
+	public abstract void setBasedir(String basedir) throws IOException;
+	
+	public abstract String getBaseDir();
+	
+	public abstract void reserveFile(String filename);
+	
+	public abstract void reserveFile(String filename, String charsetName);
+	
+	public abstract String readLine(String filename) throws IOException;
+	
+	public abstract String readLine(String filename, boolean recycle) throws IOException;
+	
+	public abstract void write(String filename, String value) throws IOException;
+	
+	public abstract void closeFiles() throws IOException;
+	
+	public abstract void closeFile(String name) throws IOException;
+	
+	protected abstract boolean filesOpen();
+	
+	public abstract File getRandomFile(String basedir, String[] extensions);
+
\ No newline at end of file
Index: src/core/org/apache/jmeter/services/FileServerNonThreading.java
===================================================================
--- src/core/org/apache/jmeter/services/FileServerNonThreading.java	(revision 0)
+++ src/core/org/apache/jmeter/services/FileServerNonThreading.java	(revision 0)
@@ -0,0 +1,280 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *  
+ */
+
+/*
+ * Created on Feb 22, 2008
+ */
+package org.apache.jmeter.services;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Random;
+
+import org.apache.jmeter.gui.JMeterFileFilter;
+import org.apache.jmeter.util.JMeterUtils;
+import org.apache.jorphan.logging.LoggingManager;
+import org.apache.log.Logger;
+
+/**
+ * 
+ * The point of this class is to provide thread-safe access to files, and to
+ * provide some simplifying assumptions about where to find files and how to
+ * name them. For instance, putting supporting files in the same directory as
+ * the saved test plan file allows users to refer to the file with just it's
+ * name - this FileServer class will find the file without a problem.
+ * Eventually, I want all in-test file access to be done through here, with the
+ * goal of packaging up entire test plans as a directory structure that can be
+ * sent via rmi to remote servers (currently, one must make sure the remote
+ * server has all support files in a relative-same location) and to package up
+ * test plans to execute on unknown boxes that only have Java installed.
+ */
+public class FileServerNonThreading extends FileServerAbstract {
+	private static final Logger log = LoggingManager.getLoggerForClass();
+
+    private static final String DEFAULT_BASE = JMeterUtils.getProperty("user.dir");
+    
+	private File base;
+
+	private final Map files = new HashMap();
+    
+
+	private final Random random = new Random();
+
+	public FileServerNonThreading() {
+		base = new File(DEFAULT_BASE);
+		log.info("Default base="+DEFAULT_BASE);
+	}
+
+    public void resetBase() throws IOException{
+        setBasedir(DEFAULT_BASE);
+    }
+    
+	public synchronized void setBasedir(String basedir) throws IOException {
+		if (filesOpen()) {
+			throw new IOException("Files are still open, cannot change base directory");
+		}
+		files.clear();
+		if (basedir != null) {
+			base = new File(basedir);
+			if (!base.isDirectory()) {
+				base = base.getParentFile();
+			}
+			log.info("Set new base="+base);
+		}
+	}
+
+	public synchronized String getBaseDir() {
+		return base.getAbsolutePath();
+	}
+
+    /**
+     * Creates an association between a filename and a File inputOutputObject,
+     * and stores it for later use - unless it is already stored.
+     * 
+     * @param filename - relative (to base) or absolute file name
+     */
+	public synchronized void reserveFile(String filename) {
+		reserveFile(filename,null);
+	}
+
+    /**
+     * Creates an association between a filename and a File inputOutputObject,
+     * and stores it for later use - unless it is already stored.
+     * 
+     * @param filename - relative (to base) or absolute file name
+     * @param charsetName - the character set encoding to use for the file
+     */
+	public synchronized void reserveFile(String filename, String charsetName) {
+		if (!files.containsKey(filename)) {
+            File f = new File(filename); 
+            FileEntry file = 
+                new FileEntry(f.isAbsolute() ? f : new File(base, filename),null,charsetName);
+            log.info("Stored: "+filename);
+			files.put(filename, file);
+		}
+	}
+
+   /**
+	 * Get the next line of the named file, recycle by default.
+	 *
+     * @param filename
+	 * @return String containing the next line in the file
+	 * @throws IOException
+	 */
+    public String readLine(String filename) throws IOException {
+      return readLine(filename, true);
+    }
+
+   /**
+	 * Get the next line of the named file.
+	 * 
+     * @param filename
+     * @param recycle - should file be restarted at EOF?
+	 * @return String containing the next line in the file (null if EOF reached and not recycle)
+	 * @throws IOException
+	 */
+	public synchronized String readLine(String filename, boolean recycle) throws IOException {
+		FileEntry fileEntry = (FileEntry) files.get(filename);
+		if (fileEntry != null) {
+			if (fileEntry.inputOutputObject == null) {
+				fileEntry.inputOutputObject = createBufferedReader(fileEntry, filename);
+            } else if (!(fileEntry.inputOutputObject instanceof Reader)) {
+                throw new IOException("File " + filename + " already in use");
+            }
+			BufferedReader reader = (BufferedReader) fileEntry.inputOutputObject;
+			String line = reader.readLine();
+			if (line == null && recycle) {
+				reader.close();
+				reader = createBufferedReader(fileEntry, filename);
+				fileEntry.inputOutputObject = reader;
+				line = reader.readLine();
+			}
+            if (log.isDebugEnabled()) log.debug("Read:"+line);
+			return line;
+		}
+		throw new IOException("File never reserved: "+filename);
+	}
+	
+	private BufferedReader createBufferedReader(FileEntry fileEntry, String filename) throws IOException { 
+		FileInputStream fis = new FileInputStream(fileEntry.file);				
+		InputStreamReader isr = null;
+        // If file encoding is specified, read using that encoding, otherwise use default platform encoding
+		String charsetName = fileEntry.charSetEncoding;
+		if(charsetName != null && charsetName.trim().length() > 0) {
+			isr = new InputStreamReader(fis, charsetName); 
+		} else {
+			isr = new InputStreamReader(fis); 
+		}
+		return new BufferedReader(isr);
+	}
+
+	public synchronized void write(String filename, String value) throws IOException {
+		FileEntry fileEntry = (FileEntry) files.get(filename);
+		if (fileEntry != null) {
+			if (fileEntry.inputOutputObject == null) {
+				fileEntry.inputOutputObject = createBufferedWriter(fileEntry, filename);
+			} else if (!(fileEntry.inputOutputObject instanceof Writer)) {
+				throw new IOException("File " + filename + " already in use");
+			}
+			BufferedWriter writer = (BufferedWriter) fileEntry.inputOutputObject;
+            if (log.isDebugEnabled()) log.debug("Write:"+value);
+			writer.write(value);
+		} else {
+            throw new IOException("File never reserved: "+filename);      
+        }
+	}
+
+	private BufferedWriter createBufferedWriter(FileEntry fileEntry, String filename) throws IOException { 
+		FileOutputStream fos = new FileOutputStream(fileEntry.file);				
+		OutputStreamWriter osw = null;
+        // If file encoding is specified, write using that encoding, otherwise use default platform encoding
+		String charsetName = fileEntry.charSetEncoding;
+		if(charsetName != null && charsetName.trim().length() > 0) {
+			osw = new OutputStreamWriter(fos, charsetName); 
+		} else {
+			osw = new OutputStreamWriter(fos); 
+		}
+		return new BufferedWriter(osw);
+	}
+
+	public void closeFiles() throws IOException {
+		Iterator iter = files.entrySet().iterator();
+		while (iter.hasNext()) {
+            Map.Entry me = (Map.Entry) iter.next();
+			closeFile((String)me.getKey(),(FileEntry)me.getValue() );
+		}
+		files.clear();
+	}
+
+	/**
+	 * @param name
+	 * @throws IOException
+	 */
+	public synchronized void closeFile(String name) throws IOException {
+		FileEntry fileEntry = (FileEntry) files.get(name);
+		closeFile(name, fileEntry);
+	}
+
+    private void closeFile(String name, FileEntry fileEntry) throws IOException {
+        if (fileEntry != null && fileEntry.inputOutputObject != null) {
+            log.info("Close: "+name);
+            if (fileEntry.inputOutputObject instanceof Reader) {
+                ((Reader) fileEntry.inputOutputObject).close();
+            } else if (fileEntry.inputOutputObject instanceof Writer) {
+                ((Writer) fileEntry.inputOutputObject).close();
+            } else { 
+                log.error("Unknown inputOutputObject type : " + fileEntry.inputOutputObject.getClass());
+            }
+			fileEntry.inputOutputObject = null;
+		}
+    }
+
+	protected boolean filesOpen() {
+		Iterator iter = files.values().iterator();
+		while (iter.hasNext()) {
+			FileEntry fileEntry = (FileEntry) iter.next();
+            if (fileEntry.inputOutputObject != null) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Method will get a random file in a base directory
+     * TODO hey, not sure this method belongs here.  FileServer is for threadsafe
+     * File access relative to current test's base directory.  
+	 * 
+	 * @param basedir
+	 * @return a random File from the basedir that matches one of the extensions
+	 */
+	public File getRandomFile(String basedir, String[] extensions) {
+		File input = null;
+		if (basedir != null) {
+			File src = new File(basedir);
+			if (src.isDirectory() && src.list() != null) {
+				File[] lfiles = src.listFiles(new JMeterFileFilter(extensions));
+				int count = lfiles.length;
+				input = lfiles[random.nextInt(count)];
+			}
+		}
+		return input;
+	}
+    
+    private static class FileEntry{
+        private File file;
+        private Object inputOutputObject; // Reader/Writer
+        private String charSetEncoding;
+        FileEntry(File f, Object o, String e){
+            file=f;
+            inputOutputObject=o;
+            charSetEncoding=e;
+        }
+    }
+}
