Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Win32/Win64 API (native code) (https://www.delphipraxis.net/17-win32-win64-api-native-code/)
-   -   Prism Filestream.Lock: Rätselhaftes Verhalten (https://www.delphipraxis.net/91164-filestream-lock-raetselhaftes-verhalten.html)

Lothar 30. Apr 2007 15:59


Filestream.Lock: Rätselhaftes Verhalten
 
Hallo!

Ich kämpfe seit 2 Tagen mit einem Problem beim Filelocking, für das ich keine Lösung finden kann. Um die Sache klarer zu machen, habe ich alles auf ein einfaches Testprogramm reduziert, wo ich die Schwierigkeit lokalisieren kann:

Ich schreibe z.B. 20.000 Mal 8 Bytes hintereinander in eine neue Datei. Dann setze ich einen Lock auf irgendeinen Datenbereich dieser 8 Bytes, z.B. beim 1000-sten Satz. Wenn ich dann mit einem zweiten Aufruf dieses Programms (Parallelausführung) auf diese 8 Bytes mit "Lesen_iClick" zugreife (oder auch mit derselben Instanz), kommt genau das erwartete Verhalten: es wird nämlich gemeldet, daß der Bereich gelocked ist. Kurioserweise bekomme ich aber bei den darunterliegenden Daten bei "Lesen_iClick" eine IOException, obwohl dort gar nichts gelocked ist! Und zwar bis zu einem Bereich von exakt Satz-Nr. 489, also ca. 4096 Bytes unterhalb; ansonsten kann ich normale Lesezugriffe machen (also vom Dateistart an bis zu diesem kritischen Bereich, also bis Satz-Nr. 488, und außerdem direkt oberhalb dem gelockten Bereich, also ab Satz 1001).

Die IOException würde ja bedeuten, daß etwas gesperrt ist, aber es ist eben NICHTS gesperrt. Trotz aller Versuche kriege ich den Fehler nicht weg! Ich wäre daher für jede Art Hilfe äußerst dankbar.

Hier Teile aus der Source:

Delphi-Quellcode:
type
   NRec = record
      a,b,c,d,e,f,g,h: byte;
   end;

   private
      N_recsize: integer;
      N_Datei: FileStream;
      procedure N_Close;
      function N_OpenCreate: boolean;
      function N_Lock (i:integer): boolean;
      function N_UnLock (i:integer): boolean;
      function N_Lesen (i:integer; var NSatz:NRec{a,b,c,d,e,f,g,h: byte}): boolean;
      procedure N_Locktest (i:integer; bLock:boolean);

...


procedure THaupt2F.N_Close;
begin
   try
      N_Datei.Free;
   except
      Msg('# N_close');
   end;
end;

function THaupt2F.N_OpenCreate: boolean;
begin
   result:=TRUE;
   try
      N_Datei:=Filestream.Create('test.dat',
         FileMode.OpenOrCreate,
         FileAccess.ReadWrite,FileShare.ReadWrite);
   except
      Msg('#OpenorCreate');
      result:=FALSE;
   end;
   N_recsize:=8;
end;

function THaupt2F.N_Lock (i:integer): boolean;
begin
   result:=TRUE;
   try
      N_Datei.Lock(i*N_recsize,1{N_recsize}); {-> der Einfachheit halber nur 1 Byte gelocked}
   except
      Msg(inttostr(i)+' schon locked');
      result:=FALSE;
   end
end;

function THaupt2F.N_UnLock (i:integer): boolean;
begin
   result:=TRUE;
   try
      N_Datei.UnLock(i*N_recsize,1{N_recsize}); {-> s.o.}
   except
      Msg('Unlock '+inttostr(i)+' fail');
      result:=FALSE;
   end;
end;

procedure THaupt2F.AnlegenSchreibenClick(Sender: TObject);
var i,j: integer;
      Writer: BinaryWriter;
      Buffer: array [1..8] of byte;
begin
   if NOT N_OpenCreate then exit;

   Writer:=BinaryWriter.Create(N_Datei);

   for i:=0 to 19999 do
      begin
         for j:=1 to 7 do Buffer[j]:=0; Buffer[8]:=i; { 1 Eintrag }
         Writer.Write(Buffer);
      end;

   N_Close;
end;

function THaupt2F.N_Lesen (i:integer; var NSatz:NRec{a,b,c,d,e,f,g,h:byte}): boolean;
var Reader: BinaryReader;
begin
   result:=TRUE;

   Reader:=BinaryReader.Create(N_Datei);

   with NSatz do
   try
      N_Datei.Seek(i*N_recsize,SeekOrigin.Begin);

      a:=Reader.ReadByte;   b:=Reader.ReadByte;
      c:=Reader.ReadByte;   d:=Reader.ReadByte;
      e:=Reader.ReadByte;   f:=Reader.ReadByte;
      g:=Reader.ReadByte;   h:=Reader.ReadByte;
   except
      on IOException do
         begin
            Msg('# IOException');
            result:=FALSE;
         end;
   end;
end;

procedure THaupt2F.Lesen_iClick(Sender: TObject);
var i:integer;
      NSatz: NRec; {a,b,c,d,e,f,g,h: byte;}
begin
   AListe.clear;

   try
      i:=strtoint(Edit1.Text);
   except
      exit
   end;

   if NOT N_OpenCreate then exit;

   if N_Lock(i) then
      begin
         if NOT N_Lesen(i,NSatz{a,b,c,d,e,f,g,h}) then
               begin N_close; exit end;
         N_UnLock(i);

         with NSatz do
            AListe.Items.add(a.ToString+' '+
               b.ToString+' '+
               c.ToString+' '+
               d.ToString+' '+
               e.ToString+' '+
               f.ToString+' '+
               g.ToString+' '+
               h.ToString);
      end;
   N_close;
end;

procedure THaupt2F.N_Locktest (i:integer; bLock:boolean);
begin
   if bLock then
      begin
         if NOT N_OpenCreate then exit;
         if N_Lock(i) then;
      end
   else
      begin
         if N_UnLock(i) then;
         N_close;
      end;
end;

...

procedure THaupt2F.Lock1000Click(Sender: TObject);
begin   N_Locktest(1000,TRUE); end;

...

procedure THaupt2F.UnLock1000Click(Sender: TObject);
begin N_Locktest(1000,FALSE); end;
Die ominöse Exception tritt beim ersten Reader.ReadByte in N_Lesen auf, nachdem das vorangegangene N_Lock(i) problemlos durchgegangen war.

Ich hoffe, ich habe mich verständlich ausgedrückt, sonst bitte nachfragen!

Grüße
Lothar

SirThornberry 30. Apr 2007 16:33

Re: Filestream.Lock: Rätselhaftes Verhalten
 
Das es sich um .Net handelt ist korrekt oder hast du dich da vertippt? Kann es sein das TFileStream von sich aus die Methode Lock gar nicht hat (zumindest unter win32 nicht)???

Khabarakh 30. Apr 2007 16:49

Re: Filestream.Lock: Rätselhaftes Verhalten
 
@SirT: Siehst du irgendwo einen Konstruktoraufruf von TFileStream :zwinker: ?

@Lothar: 4096 Byte ist exakt die Standardgröße des FileStream-Buffers, das kann kein Zufall sein. Auch wenn du ein paar Byte unter der gelockten Adresse schreiben willst, wird der Stream wohl(nicht gerade mein Fachgebiet) den ganzen Buffer beim Flush auf die Festplatte schreiben, was logischerweise nicht problemlos verlaufen wird.

PS: Mir sind schon viele lustige Member-Präfixe begegnet, aber "N_" ist mal was Neues :mrgreen: .

SirThornberry 30. Apr 2007 17:10

Re: Filestream.Lock: Rätselhaftes Verhalten
 
wo findet sich FileStream? Habs weder in der Hilfe noch per Codevervollständigung gefunden.

Khabarakh 30. Apr 2007 17:23

Re: Filestream.Lock: Rätselhaftes Verhalten
 
System.IO.FileStream, Assembly mscorlib, .Net Base Class Library

alcaeus 30. Apr 2007 17:52

Re: Filestream.Lock: Rätselhaftes Verhalten
 
Moin,

schreibt FileStream direkt auf die Platte? In dem Fall wuerde ich davon ausgehn, dass der gesamte Cluster gesperrt wird, in dem der zu sperrende Bereich liegt. :gruebel:

Greetz
alcaeus

Lothar 30. Apr 2007 18:32

Re: Filestream.Lock: Rätselhaftes Verhalten
 
Hallo!

Danke für die Tips und Hinweise, aber das hat mich noch nicht weitergebracht.
Daß das Programm so wunderbar funktioniert und nur genau den betreffenden Satz sperrt (wie auch in der .NET-Anleitung beschrieben - dazu gibt es ja auch Beispiele), habe ich bereits erwähnt. Ominös ist nur, daß in diesem Bereich "unterhalb" der gelockten Sektion systematische Lesefehler auftreten. Das ist aber nirgendwo beschrieben oder erwähnt und entspricht auch keinesfalls der .NET-Dokumentation. Von "Clustern" ist da keine Rede. Also entweder das Framework ist hier fehlerhaft oder ich habe falsch programmiert. Die Antwort darauf würde mich aber brennend interessieren, weil ich auf die Funktion dringend angewiesen bin.

Gruß
Lothar

Lothar 30. Apr 2007 19:40

Re: Filestream.Lock: Rätselhaftes Verhalten
 
Hallo!

Ok - das mit den 4096 Bytes stimmt! Ich habe spaßeshalber in meinem Beispielprogramm die Satzlänge von 8 auf 4096 Bytes erhöht. Diesmal läuft das Program ohne jeden Fehler. Verkürze ich die Satzlänge aber auf 4095 Bytes, so kommt die Exception (bei gelocktem Satz 1000) nur noch im Satz 999.

Nur hilft mir das auch nicht weiter, denn ich benutze nur Dateien mit kleineren Satzlängen - und was mache ich dann da?
Außerdem finde ich es schon seltsam, daß das nirgendwo in der MSDN-Bibliothek erwähnt wird.

Lothar

bit4bit 30. Apr 2007 21:02

Re: Filestream.Lock: Rätselhaftes Verhalten
 
... mal ne grundsätzliche Überlegung...

Record-Locking soll doch normalerweise verhindern, dass die Datensätze einer Datei inkonsistent werden. Dazu ist es notwendig, dass "mindestens" die gewünschten Daten gesperrt werden (mehr Daten dürfen jederzeit gesperrt werden).

Ist die Transaktion beendet, sollten die gesperrten Daten sofort wieder freigegeben werden.

Jedes Programm muss also darauf gefasst sein, dass ein Zugriff scheitert und damit auch so umgehen können, dass keine Deadlock-Situation eintritt.

-- Übrigens konnte ich nirgendwo finden, dass "nur" die gelockten Bytes gesperrt werden, wo hast Du das gefunden?

Das Lock muss an der Stelle problemlos durchgehen, sonst würdest Du ja Deine Reservierung nicht anmelden können.
Denk mal an "first come - first served".

Mit Clustern hat das Ganze übrigens IMHO nichts zu tun, sonder mit der Buffer-Size, wie Khabarakh schon sagte.

Sag doch mal bitte warum Dich dieses Verhalten eigentlich so behindert.

Gruß, bit4bit

Lothar 30. Apr 2007 21:59

Re: Filestream.Lock: Rätselhaftes Verhalten
 
Hallo!

Danke für die Nachfragen!
Es ist ganz einfach: Ich teste gerade eine Portierung einer Win32-Anwendung. In Win32 gibt es "file of (record)". Da ist es ganz simpel: Wie Du sagst wird recordweise gelocked. Bei .NET gibt es aber kein "file of (record)" mehr, sondern nur noch Filestreams. D.h. auf die Datei wird byteweise sukzessive zugegriffen. Mit 'seek' oder 'position' setzt man an der Stelle, wo der Record ist, auf und liest dann den entsprechenden Bereich. Man muß also den Start ausrechnen und die Länge des Records lesen - bzw. hier: locken.

Selbstverständlich soll aber nur der Bereich des Record gelocked werden und nichts sonst. Der wird hier auch sauber gesperrt, aber was dann passiert, ist, daß ein darunter/davorliegender Bereich von ca. 4 K nicht mehr lesbar ist (es kommt die besagte Exception). Das ist natürlich indiskutabel.

Ich habe mir in den letzten Tagen alle im Web auffindbaren Beispiele von Filestream.Lock angeschaut. Da steht es auch entsprechend. Aber von dem Lesefehler steht nirgends etwas erwähnt.

Ich benutze übrigens mit Win32 auch schon seit längerem TFilestream. Da geht das Locking über

LockFile(hZei,Recordnumber * sizeof(NSatz),0,sizeof(NSatz),0);
[wobei hZei:=FileOpen(N_Name,fmReadWr+fmDenyNone);]


also im Grunde analog, mur mit Datei-Handle. Und das Lesen geht mit

FileSeek(hZei,Recordnumber * sizeof(NSatz),0); und
FileRead(hZei,NSatz,SizeOf(NSatz));


Der riesige Vorteil ist hier, daß ich mit einem Befehl den ganzen Record auslesen kann, mit der ganzen heterogenen Datenstruktur. In .NET muß ich aber byte- und char-weise hintereinander auslesen. Aber das nur nebenbei...

Gruß
Lothar


Alle Zeitangaben in WEZ +1. Es ist jetzt 20:53 Uhr.
Seite 1 von 2  1 2      

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz