# Buffered Reader Bug!



## VoiDee (12. Mai 2008)

Hi,

ich habe ein kleines, aber nachhaltiges Problem mit dem Einlesen einer Textdatei:

Folgender Code funktioniert einwandfrei, wenn man in lokal ausführt:

```
public int copy( String p_source, String p_target)  {
  BufferedReader     l_reader = null;
  BufferedWriter     l_writer = null;
  
  int            l_cntLine = 0;
  String         l_line    = null;

  try {
    l_reader = new BufferedReader( new FileReader(p_source));
    l_writer = new BufferedWriter( new FileWriter(p_target,true) );
    
  
    while ( (l_line=l_reader.readLine()) !=null )
    {
      l_writer.write(l_line);
      l_writer.newLine();
      l_cntLine++;
    }
    
  }
  catch(Exception ex) {
    ex.printStackTrace();
  }
  finally {
    try {
      l_writer.flush();
      l_writer.close();
    } catch(Exception e) {
      e.printStackTrace();
    }

    try {
      l_reader.close();
      
    } catch(Exception e) {
      e.printStackTrace();        
    }

    l_writer = null;
    l_reader = null;

  }
  return l_cntLine;
  
}  // -- eof
```
Lokal bedeutet dabei, dass ich eine Main-Methode in der selben Klasse habe und von dort aus die Funktion copy() aufrufe. Die Quelldatei ist eine ASCII-Datei, in der 336 Textzeilen enthalten sind und die Datei ist 20.143 Bytes groß.

So weit - so gut. Lokal funktioniert das einwandfrei 

Nun zu meinem Problem: wenn ich die Funktion aus einer RMI-Server Umgebung heraus aufrufe werden nur 8.192 Bytes (=interen Größe des von BufferedReader verwendeten Buffers) gelesen. Dann wird das Einlesen beendet, dass heisst es wird nicht mal eine Exception erzeugt. Der BufferedReader liefert ein _end of file_, so dass ich keine Ahnung habe, wie ich das Problem umgehen könnte. 
Die geschriebene Zieldatei ist genau 8.192 Bytes groß.

Alle meine Versuche mit _mark, skip, mit der read-Methode die Daten "anders" einzulesen führten nur zu einem Ergebnis: das Lesen wird bei Erreichen der Buffergröße beendet.

Irgendjemand eine Idee, wie man diesen Bug beheben kann bzw. was das grundlegende Problem sein könnte? 

Gruß
voidee

PS: ich jab's unter JRE 1.5 und aktuellem 1.6 getestet, überall das selbe Problem_


----------



## SlaterB (12. Mai 2008)

schließ mal ein paar Fehlerquellen aus:
lies die Datei nur ein, entferne allen anderen Code,

gibt den absoluten Pfad der Datei aus und die Dateilänge,

teste mit einer neuen Datei "test.txt", schreibe dort irgendeinen neuen Text rein und lasse dir diesem vom Programm Zeile für Zeile ausgeben um ganz sicherzugehen, dass nicht eine falsche Datei gelesen wird
(wenns beim RMI-Server schlecht mit log4j/ Konsole System.out/ Debuggen ist, dann schreibe doch wieder in eine Datei)

teste es mit einer 100 Byte-Datei, dann 1000, 5000, 8000, 10000 usw. ob dabei irgendwas festzustellen ist

setze einen größeren/ kleineren Buffer, verwende einen reinen FileReader, auch mal RandomAccessFile


edit: kann es sein, dass nur die neu geschriebene Datei falsch ist?
hast du irgendwelche anderen Log-Infos zum Einlesevorgang?
erstelle notfalls eine neue Dummy-Datei, die im Dateinamen die im Programm gezählten Anzahl eingelesener Zeilen/ Bytes hat 


edit:
> dass heisst es wird nicht mal eine Exception erzeugt. 

wie stellst du das fest?


----------



## Guest (12. Mai 2008)

Erst mal vielen Dank für deine Tipps.

1) wenn ich die Datei im "RMI-Umfeld" einlese, werden nur 137 Zeilen (genauer gesagt bis zu der Stelle wo das 8192 Bytes der Quelldatei ist) gelesen. Es fehlen also fast 200 Zeilen.

2) da die Datei gelesen wird (einlesen mit readLine() und Ausgabe an System.out()) liegt kein "einfacher" Fehler vor. Datei/Dateipfad stimmt. Das sehe ich ja auch, wenn ich die Funktion "stand-alone" ausführe. Dann wird die Zieldatei ja komplett kopiert (also alle Zeilen, vollständig bis zum letzten Byte).

3) einen größeren Buffer möchte ich nicht setzen weil ich späten mal wirklich richtig große Dateien kopieren möchte

4) das mit der 100Byte usw. Datei schenke ich mir, da ich ja weiss, dass meine Zieldatei bis 8.192 Bytes geschreiben wird, bzw. besser gesagt, ich weiss, dass der READER nur 8192 Bytes liest. Woher weiss ich das?
Ich habe z.B. 3 Buffer a 8.192 angelegt und 3 mal gelesen, in etwa so:

```
byte[] buffer_1 = new buffer[8192];
byte[] buffer_2 = new buffer[8192];
byte[] buffer_3 = new buffer[8192];

int l_read = 0;

try {
  fis = new FileInputStream(p_source);
  
  l_read = fis.read(l_buffer_1);
  System.out.println( "read=" + Integer.toString(l_read));
	
  l_read = fis.read(l_buffer_2);
  System.out.println( "read=" + Integer.toString(l_read));

  l_read = fis.read(l_buffer_3);
  System.out.println( "read=" + Integer.toString(l_read));
  
}
```
Die erste Ausgabe ergibt 8192, die zweite und dritte jeweils -1, d.h. er liest nur bis 8192 Bytes ein und erkennt dann ein end-of-file.

5) mit kleinerem Buffer habe ich auch schon versucht. Resultat: leider das selbe 

6) _Ist die geschriebene Datei falsch?_ Nein. Bis zu der Stelle wo tatsächlich geschreiben wird stimmen Quell-Datei und Zieldatei überein.

7) _wie stellst du das fest?_
mhhh. da ist ein try-catch-Block  und es wird kein Stacktrace ausgegeben  :shock: 


Wie gesagt, wenn ich die Funktion "direkt" starte funktioniert das Kopieren ja auch. Nur im RMI Umfeld ist irgendetwas anders, so dass dort ein anderes Verhalten auftritt. Ich hatte schon den RMISecurityManager im Verdacht, aber da konnte ich auch noch nichts finden.
Ich habe auch das _java.policy_ File mal angepasst und mir eine <<ALL FILES>> Permission erteilt, um zu verhindern, dass die JVM mich behindert.

Ein Wort noch, warum ich den RMISecurityManager ausschliesse. Zunächst mal, weil ich einen eigenen geschrieben habe und mir alle Aufrufe ausgeben lasse. Aber: die ersten 8192 Bytes lässt er ja zu, d.h. ich kann die Quelldatei lesen und die Zieldatei erzeugen und da hineinschreiben.



Wenn ich nur wüsste was da anders ist.....


----------



## SlaterB (12. Mai 2008)

>> wie stellst du das fest?
> mhhh. da ist ein try-catch-Block und es wird kein Stacktrace ausgegeben 

mich verwunderte, dass du so eine seltsame zweite Datei schreibst und kein System.out.println verwendest

> liegt kein "einfacher" Fehler vor [/] schenke ich mir

gib dann wenigestens nochmal File.length() aus und versuche es mit einer neuen sauberen Textdatei mit geänderten Namen und geänderten Inhalt, nicht dass da EOF mitten im Text steht 
(wobei ich aber gerade nicht 100% sagen kann, ob das überhaupt möglich ist  ),

außer diesen einfachen Dingen kann ich aber nix beitragen


----------



## Voidee (12. Mai 2008)

> >> wie stellst du das fest?
> > mhhh. da ist ein try-catch-Block und es wird kein Stacktrace ausgegeben
> 
> mich verwunderte, dass du so eine seltsame zweite Datei schreibst und kein System.out.println verwendest


So? Was ist daran verwunderlich?

Ich benötige diesen Copy Algorythmus weil ich später mal ca. 100.000 dieser kleinen Dateien zu einer großen Datei zusammenkopieren möchte. Ich möchte vermeiden, dass parallele Prozesse in die spätere - große Zieldatei - schreiben. Vermutlich kann man schon aus BS Gründen nicht parallel in eine Datei schreiben. Deshalb soll ein sogenannter "FileCopyHandler" eine kleine Quelldatei nach der anderen die große Zieldatei schreiben.
Diese große Datei werde ich dann als Oracle External Table behandeln und in die Oracle-Datenbank laden. Daten auf diese Art in die DB zu laden ist viel (!) performanter als jeden Datensatz einzeln oder jede kleine Datei für sich.
DAS ganze dann etwa 60 - 100fach.
Wenn du es ganz genau wissen willst: ich muss ca. 8 Millionen XML-Anfragen gegen eine Oracle DB sendem. Das Ergebnis ist ein XML-Stream. Die darin enthaltenen Daten extrahiere ich mit dem Java Programm und lege die reinen Daten als kleine Datei ab (das ist meine Quelldatei mit den 336 Zeilen). Diese Dateien führe ich dann je Organisationseinheit zu einer großen Zieldatei zusammen und gebe sie einer zweiten Oracle-DB. 
Und - nur um dieser Frage auch vorzukommen - nein, die beiden Oracle-DB dürfen aus sicherheitsgründen nicht direkt miteinander kommunizieren und die Daten können (leider) auch nicht mittels einer View einfach abgeholt werden. Das ist nicht meine Entscheidung sondern haben ein paar richtige große Dummköpfe so entschieden, die dafür auch noch viel mehr Geld als ich verdienen.

Eine Ausgabe nach "System.out" bringt mir überhaupt nichts. So wie Dir - verzeih mir ist nicht persönlich gemeint - es nicht weiterhilft, dass du den genaueren Hintergrund kennst.



> gib dann wenigestens nochmal File.length() aus und versuche es mit einer neuen sauberen Textdatei mit geänderten Namen und geänderten Inhalt, nicht dass da EOF mitten im Text steht
> (wobei ich aber gerade nicht 100% sagen kann, ob das überhaupt möglich ist icon_wink.gif ),
> 
> außer diesen einfachen Dingen kann ich aber nix beitragen


Geht das genauer? 
File length der Quelldatei oder der Zieldatei? Glaub mir doch, wenn ich sage, dass es so geschreiben wird wie ich es sage. Ich seh die Größen der Dateien doch im Explorer. File length sagt da mal gar nicht anderes.

Warum denkst du, dass mit einer "sauberen" Textdatei etwas anderes herauskommt. Und was ist eine saubere Zieldatei? Ist meine Quelldatei, in der nur Zahlen, Semikolon und CRLF vorkommen unsauber?


----------



## Körby (13. Mai 2008)

Versuch es das datenempfangen mal in einer whileschleife:

--> while ((msg = br.readLine()) != null)

Dann sollte er solange die Schleife durchlaufen, bis die gesamte Datei empfangen ist und nicht nur, bis der Buffer voll ist.


----------



## SlaterB (13. Mai 2008)

Voidee hat gesagt.:
			
		

> So? Was ist daran verwunderlich?
> 
> Ich benötige diesen Copy Algorythmus weil ich später mal [..]


das ist doch für die Problemstellung egal,
ich überlegte halt, wie du initial feststellst, dass die Datei nicht groß genug ist,
du hattest keine Ausgaben sondern nur die zweite Datei was nicht gerade direkt ist,
daher vermutete ich dass du z.B. kein Log hast um das ganze viel leichter zu kontrollieren,

egal, nun hast du ja Log und kannst loggen wie du es willst


> So wie Dir - verzeih mir ist nicht persönlich gemeint - es nicht weiterhilft, dass du den genaueren Hintergrund kennst.


hmm, ich will ihn ja auch gar nicht kennen 




> Geht das genauer?
> File length der Quelldatei oder der Zieldatei? Glaub mir doch, wenn ich sage, dass es so geschreiben wird wie ich es sage. Ich seh die Größen der Dateien doch im Explorer. File length sagt da mal gar nicht anderes.
> 
> Warum denkst du, dass mit einer "sauberen" Textdatei etwas anderes herauskommt. Und was ist eine saubere Zieldatei? Ist meine Quelldatei, in der nur Zahlen, Semikolon und CRLF vorkommen unsauber?



was interessiert mich die Zieldatei? die will ich doch die ganze Zeit schon rausschmeißen 
es geht allein um die Quelldatei,
das Programm sagt, dass sie 8192 Bytes groß ist,

ich vermute eben dass Java nicht so ganz falsch arbeitet,
sondern dass du evtl. die falsche Datei auswählst oder sonstwas,
gleicher Dateiname in anderem Verzeichnis bei relativer Pfadfindung usw.,
anderes Betriebssystem mit anderer Interpretation des Dateiinhalts usw.,

wenn du derartiges nicht prüfen willst oder schon hast, dann eben nicht, habe nur meine Tipps zum Besten gegeben


----------



## Guest (13. Mai 2008)

> Versuch es das datenempfangen mal in einer whileschleife:
> 
> --> while ((msg = br.readLine()) != null)
> 
> Dann sollte er solange die Schleife durchlaufen, bis die gesamte Datei empfangen ist und nicht nur, bis der Buffer voll ist.


Hab ich gemacht. Hat aber kein anderes Ergebnis gebracht.



> was interessiert mich die Zieldatei? die will ich doch die ganze Zeit schon rausschmeißen icon_wink.gif
> es geht allein um die Quelldatei,
> das Programm sagt, dass sie 8192 Bytes groß ist,


Mhhh. Guter Einwand 



> ch vermute eben dass Java nicht so ganz falsch arbeitet,
> sondern dass du evtl. die falsche Datei auswählst oder sonstwas,
> gleicher Dateiname in anderem Verzeichnis bei relativer Pfadfindung usw.,
> anderes Betriebssystem mit anderer Interpretation des Dateiinhalts usw.,
> ...



Von nicht prüfen wollen, kann nicht die Rede sein. Es ist nur so, wie ich es weiter oben schon versucht habe anzudeuten: wenn ich die copy() Funktion aus der FileCopy Klasse heraus aufrufe, funktioniert es einwandfrei. Also, es ist die selbe Klasse auf dem selben Rechner mit dem selben BS und der selben JRE.
Wenn ich die selbe Klasse mit der selben Methode und den (inhaltlich identischen) Aufrufparamtern aus meiner RMI Server Klasse heraus aufrufe, klappt es nicht. Die Stichworte "BufferedReader Bug" sind Google nicht ganz unbekannt, aber weiterhelfen tut's leider auch nicht.

Inzwischen habe ich meinen sogenannten "File Copy Handler" ein bisschen umgeschrieben: er ist nun ein Thread, der alle 10 Sekunden nachschaut, ob er in seiner "Queue" neue Anweisungen zum Kopieren hat. Der Client erhält unmittelbar nach dem Kopierauftrag den Fokus zurück (wartet also nicht auf das Ende des Kopiervorgangs wie bisher). Der Client wird nach dem Kopieren über eine Callback-Methode über das Ende des Kopierens informiert.

Ich vermute mal, dass das Problem nicht am Reader selbst liegt, sondern das die Thread-Synchronisation, die zwischen den RMI Komponenten stattfindet, irgendwie damit zu tun hat. Zur Zeit liest der File Copy Handler die Quelldatei vollständig ein (und die Zieldatei wird vollständig geschreiben)

Leider kann ich die (vorläufige) Lösung nicht genauer sagen, da sich mir das eigentliche Ursache immer noch nicht erschlossen hat.

Morgen gibt's dann mal einen Streßtest mit 100 parallel Dateien.


----------

