Menu

#43 DefaultCsvEncoder StringIndexOutofBoundsException

Outstanding
closed
nobody
None
1
2015-01-04
2013-05-10
No

I am getting the following StringIndexOutOfBoundsException:

Caused by: java.lang.StringIndexOutOfBoundsException
    at java.lang.AbstractStringBuilder.delete(AbstractStringBuilder.java:702)
    at java.lang.StringBuilder.delete(StringBuilder.java:255)
    at org.supercsv.encoder.DefaultCsvEncoder.encode(DefaultCsvEncoder.java:42)
    at org.supercsv.io.AbstractCsvWriter.escapeString(AbstractCsvWriter.java:102)
    at org.supercsv.io.AbstractCsvWriter.writeRow(AbstractCsvWriter.java:196)
    at org.supercsv.io.AbstractCsvWriter.writeRow(AbstractCsvWriter.java:162)
    at org.supercsv.io.CsvListWriter.write(CsvListWriter.java:79)

This is occurring when I am running multiple instances of the CsvListWriter concurrently. Here's what I think is happening:

CsvPreference is constructed with:

CsvPreference pref = new CsvPreference.Builder(CsvPreference.STANDARD_PREFERENCE)
            .useQuoteMode(new ColumnQuoteMode(1,2,3,6)).build();

Based on ​CsvPreference, the CsvEncoder is coming from CsvPreference.STANDARD_PREFERENCE. But CsvPreference.STANDARD_PREFERENCE is static, which means all instances of CsvPreference are sharing the same CsvEncoder instance.

​DefaultCsvEncoder re-uses its instance of StringBuilder. But when there are multiple threads, there might be concurrent calls to DefaultCsvEncoder.encode(). While probably resulting in gobly-gook, it can also lead to a StringIndexOutofBoundsException for the following line:

currentColumn.delete(0, currentColumn.length()); // reusing builder object

Related

Bugs: #43

Discussion

  • James Bassett

    James Bassett - 2013-05-14

    Well spotted. Super CSV is not really intended to be used multithreaded, so I hadn't even thought about that! The only workaround is to create a new preferences object for each thread - you could build each one from an existing preference, and just pass in a new instance of DefaultCsvEncoder via the useEncoder() method.

    I'll have to have a think about this one...

     
  • Stuart Rossiter

    Stuart Rossiter - 2013-06-06

    I had a very similar problem in a multi-threaded scenario running with CsvMapWriter. The symptom this time is an ArrayIndexOutOfBoundsException.

    ArrayIndexOutOfBoundsException: -4
    at java.lang.StringBuilder.append(StringBuilder.java:203)
    at org.supercsv.encoder.DefaultCsvEncoder.encode(DefaultCsvEncoder.java:81)

    I'll see if I can code the workaround you (James) suggested and let you know if that works.

     

    Last edit: Stuart Rossiter 2013-06-06
  • Stuart Rossiter

    Stuart Rossiter - 2013-06-07

    I can confirm that I could bypass the problems by creating a CsvPreference instance per thread, each with its own DefaultCsvEncoder.

     
  • FAZ

    FAZ - 2014-02-14

    Stuart,
    Even am facing the same issue , could you please share your code sample?

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    OutputStreamWriter outputwriter = new OutputStreamWriter(baos);
    ICsvBeanWriter beanWriter = null;
    try {
    beanWriter = new CsvBeanWriter(outputwriter,CsvPreference.STANDARD_PREFERENCE);

     

    Last edit: FAZ 2014-02-14
  • FAZ

    FAZ - 2014-02-14

    Hi James, am trying something below but i think there is some obvious mistake, could you pls help me here.
    CsvPreference cp = CsvPreference.STANDARD_PREFERENCE;
    System.out.println("cp : "+cp);
    System.out.println("cp encoder : "+cp.getEncoder());
    Builder d = new Builder(cp);
    d.useEncoder(new DefaultCsvEncoder());
    beanWriter = new CsvBeanWriter(outputwriter,cp);
    System.out.println("Builder : "+d);
    System.out.println("beanWriter : "+beanWriter);
    System.out.println("cp after: "+cp);
    System.out.println("cp encoder after: "+cp.getEncoder());
    thanks

     
  • James Bassett

    James Bassett - 2014-02-15

    Hi Faz,

    You aren't actually using the new preferences you've created.

    You'll need to change the following line

    beanWriter = new CsvBeanWriter(outputwriter,cp);
    

    to

    beanWriter = new CsvBeanWriter(outputwriter,d.build());
    

    Remember you can create your new preferences in one go:

    CsvPreference preference = 
        new CsvPreference.Builder(CsvPreference.STANDARD_PREFERENCE)
        .useEncoder(new DefaultCsvEncoder())
        .build();
    
     
    • FAZ

      FAZ - 2014-02-15

      Thanks a lot James, the second option that you mentioned was indeed what I
      tried and it worked,

      On Sat, Feb 15, 2014 at 2:31 AM, James Bassett jamesbassett@users.sf.netwrote:

      Hi Faz,

      You aren't actually using the new preferences you've created.

      You'll need to change the following line

      beanWriter = new CsvBeanWriter(outputwriter,cp);

      to

      beanWriter = new CsvBeanWriter(outputwriter,d.build());

      Remember you can create your new preferences in one go:

      CsvPreference preference =
      new CsvPreference.Builder(CsvPreference.STANDARD_PREFERENCE)
      .useEncoder(new DefaultCsvEncoder())
      .build();


      Status: open
      Created: Fri May 10, 2013 04:26 PM UTC by Justin Littman
      Last Updated: Fri Feb 14, 2014 03:46 PM UTC
      Owner: nobody

      I am getting the following StringIndexOutOfBoundsException:
      Caused by: java.lang.StringIndexOutOfBoundsException
      at java.lang.AbstractStringBuilder.delete(AbstractStringBuilder.java:702)
      at java.lang.StringBuilder.delete(StringBuilder.java:255)
      at org.supercsv.encoder.DefaultCsvEncoder.encode(DefaultCsvEncoder.java:42)
      at
      org.supercsv.io.AbstractCsvWriter.escapeString(AbstractCsvWriter.java:102)
      at org.supercsv.io.AbstractCsvWriter.writeRow(AbstractCsvWriter.java:196)
      at org.supercsv.io.AbstractCsvWriter.writeRow(AbstractCsvWriter.java:162)
      at org.supercsv.io.CsvListWriter.write(CsvListWriter.java:79)

      This is occurring when I am running multiple instances of the
      CsvListWriter concurrently. Here's what I think is happening:

      CsvPreference is constructed with:
      CsvPreference pref = new
      CsvPreference.Builder(CsvPreference.STANDARD_PREFERENCE)
      .useQuoteMode(new ColumnQuoteMode(1,2,3,6)).build();
      Based on CsvPreference, the CsvEncoder is coming from
      CsvPreference.STANDARD_PREFERENCE. But CsvPreference.STANDARD_PREFERENCE is
      static, which means all instances of CsvPreference are sharing the same
      CsvEncoder instance.

      DefaultCsvEncoder re-uses its instance of StringBuilder. But when there
      are multiple threads, there might be concurrent calls to
      DefaultCsvEncoder.encode(). While probably resulting in gobly-gook, it can
      also lead to a StringIndexOutofBoundsException for the following line:
      currentColumn.delete(0, currentColumn.length()); // reusing builder object


      Sent from sourceforge.net because you indicated interest in
      https://sourceforge.net/p/supercsv/bugs/43/

      To unsubscribe from further messages, please visit
      https://sourceforge.net/auth/subscriptions/

       

      Related

      Bugs: #43

  • David Shay

    David Shay - 2014-06-26

    I also fell for that problem. The useEncoder() workaround works for me.

    As a bugfix, why not simply create a new StringBuilder for every method call? The performance penalty must be quite minimal, no?

     
  • James Bassett

    James Bassett - 2014-06-26

    Hi David,

    Yeah Kasper is keen on optimizing, and I've tried to keep that idea in mind. But in this case I think you're right and we should take a small hit to make it more thread-safe. It was probably a bad idea to have an enum (preference) have an instance of a non-thread-safe encoder....things you don't think of until people start using the library in ways you didn't think of!

     
  • James Bassett

    James Bassett - 2014-06-28

    Fixed in [r303]

     

    Related

    Commit: [r303]

  • James Bassett

    James Bassett - 2014-06-28
    • status: open --> pending
     
  • Karthik Kumar

    Karthik Kumar - 2014-08-27

    Just wondering if this could potentially also be an issue in org.supercsv.io.Tokenizer with multiple instances of CsvListReader reusing the same tokenizer. I see that in Tokenizer.java, we are reusing a StringBuilder called currentColumn. If this was executed concurrently,

    currentColumn.setLength(0);

    in readColumns() might screw things up?

     

    Last edit: Karthik Kumar 2014-08-27
  • James Bassett

    James Bassett - 2015-01-04
    • Status: pending --> closed
     

Log in to post a comment.

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.