View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
0038306 | FPC | FCL | public | 2021-01-04 14:15 | 2021-01-27 16:20 |
Reporter | Thomas Bulla | Assigned To | Michael Van Canneyt | ||
Priority | normal | Severity | minor | Reproducibility | always |
Status | resolved | Resolution | fixed | ||
Product Version | 3.2.0 | ||||
Fixed in Version | 3.3.1 | ||||
Summary | 0038306: gqueue unit memory access error | ||||
Description | Hello, the gqueue unit make problems. If I take a big queue it takes memory access error. I think it is in the IncreaseCapacity routine. I have build a little program to demonstrate the problem. | ||||
Additional Information | program gqueue_test; uses gqueue; type TIntQueue = specialize TQueue<Integer>; var IntQueue: TIntQueue; PushCnt: Integer; procedure Push2Pop1; var i: Integer; begin for i:= 0 to 1000000 do begin IntQueue.Push(PushCnt); inc(PushCnt); IntQueue.Push(PushCnt); inc(PushCnt); IntQueue.Pop(); end; end; var i: Integer; begin try IntQueue:= TIntQueue.Create; Push2Pop1; WriteLn('Ready'); ReadLn; finally IntQueue.Free; end; end. | ||||
Tags | No tags attached. | ||||
Fixed in Revision | 48405 | ||||
FPCOldBugId | |||||
FPCTarget | - | ||||
Attached Files |
|
child of | 0034420 | closed | Michael Van Canneyt | More memory efficient TDeque.IncreaseCapacity |
|
gqueue_test.lpr (528 bytes)
program gqueue_test; uses gqueue; type TIntQueue = specialize TQueue<Integer>; var IntQueue: TIntQueue; PushCnt: Integer; procedure Push2Pop1; var i: Integer; begin for i:= 0 to 1000000 do begin IntQueue.Push(PushCnt); inc(PushCnt); IntQueue.Push(PushCnt); inc(PushCnt); IntQueue.Pop(); end; end; var i: Integer; begin try IntQueue:= TIntQueue.Create; Push2Pop1; WriteLn('Ready'); ReadLn; finally IntQueue.Free; end; end. |
|
When i = 262143 a call to TDeque.IncreaseCapacity is made and it tries to increase capacity from $40000 to $48000. In this loop: for i:=0 to FStart-1 do FData[OldEnd+i]:=FData[i]; a range check error occurs. It tries to do: FData[$48000]:=FData[$8000], but High(FData)=$47FFF. Range check is off in this unit (by default) and then the next Push() operation triggers an Invalid pointer operation. Notice that up to that time, TDeque.IncreaseCapacity was called 17 times already without it triggering any fault. |
|
This is the sequence of increasing capacity: TDeque.IncreaseCapacity: 00004 to 00008, FStart=00003, OldEnd=00004, OldEnd+FStart-1=00006, High(FData)=00007 TDeque.IncreaseCapacity: 00008 to 00010, FStart=00007, OldEnd=00008, OldEnd+FStart-1=0000E, High(FData)=0000F TDeque.IncreaseCapacity: 00010 to 00020, FStart=0000F, OldEnd=00010, OldEnd+FStart-1=0001E, High(FData)=0001F TDeque.IncreaseCapacity: 00020 to 00040, FStart=0001F, OldEnd=00020, OldEnd+FStart-1=0003E, High(FData)=0003F TDeque.IncreaseCapacity: 00040 to 00080, FStart=0003F, OldEnd=00040, OldEnd+FStart-1=0007E, High(FData)=0007F TDeque.IncreaseCapacity: 00080 to 00100, FStart=0007F, OldEnd=00080, OldEnd+FStart-1=000FE, High(FData)=000FF TDeque.IncreaseCapacity: 00100 to 00200, FStart=000FF, OldEnd=00100, OldEnd+FStart-1=001FE, High(FData)=001FF TDeque.IncreaseCapacity: 00200 to 00400, FStart=001FF, OldEnd=00200, OldEnd+FStart-1=003FE, High(FData)=003FF TDeque.IncreaseCapacity: 00400 to 00800, FStart=003FF, OldEnd=00400, OldEnd+FStart-1=007FE, High(FData)=007FF TDeque.IncreaseCapacity: 00800 to 01000, FStart=007FF, OldEnd=00800, OldEnd+FStart-1=00FFE, High(FData)=00FFF TDeque.IncreaseCapacity: 01000 to 02000, FStart=00FFF, OldEnd=01000, OldEnd+FStart-1=01FFE, High(FData)=01FFF TDeque.IncreaseCapacity: 02000 to 04000, FStart=01FFF, OldEnd=02000, OldEnd+FStart-1=03FFE, High(FData)=03FFF TDeque.IncreaseCapacity: 04000 to 08000, FStart=03FFF, OldEnd=04000, OldEnd+FStart-1=07FFE, High(FData)=07FFF TDeque.IncreaseCapacity: 08000 to 10000, FStart=07FFF, OldEnd=08000, OldEnd+FStart-1=0FFFE, High(FData)=0FFFF TDeque.IncreaseCapacity: 10000 to 20000, FStart=0FFFF, OldEnd=10000, OldEnd+FStart-1=1FFFE, High(FData)=1FFFF TDeque.IncreaseCapacity: 20000 to 40000, FStart=1FFFF, OldEnd=20000, OldEnd+FStart-1=3FFFE, High(FData)=3FFFF TDeque.IncreaseCapacity: 40000 to 48000, FStart=3FFFF, OldEnd=40000, OldEnd+FStart-1=7FFFE, High(FData)=47FFF: OldEnd+i > High(FData): 48000 > 47FFF The loop tries to copy the "old size" number of items to the end of the queue (?), but that fails of course if the newly added chunk is not the same size as the old size of the data. It seems that also the index calculation in TDeque.PushBack also depends on the size being doubled?? |
|
Hello Bart, thanks for your support. What is the next step? |
|
> thanks for your support. What is the next step? I'm not an fpc devel, I just tried to narrow things down. Maybe I can come upt with a fix, otherwise it's up to another fpc devel. |
|
Ok, I must wait. I have noticed that, the error occurs when the memory is not doubled for the first time. (FCapacity - OldEnd) < (FStart) and FStart is the Front of the Queue. |
|
> I have noticed that, the error occurs when the memory is not doubled for the first time. Yes, I already mentioned that: "but that fails of course if the newly added chunk is not the same size as the old size of the data." |
|
Let me see if I understand the algorithm. When we resize, we add a chunk of memory tot the array. We then try to move all elements that come before "Front" (in the array) to a location after "Back" (in the array). If you don't move all elements that come before "Front" you break the circular buffer (that the array represents). This moving then can only succeed if the number of elements to move is smaller than the newly added element-space. I guess that NOT moving ther elements (is there is no room for it) does not break the circular buffer mechanism, but I am not really sure. The condition for moving elements would then become: if (FStart>0) and (FCapacity-OldEnd>=FStart) then begin |
|
Do we have any sort of integrity test for TDeque? |
|
> if (FStart>0) and (FCapacity-OldEnd>=FStart) then That won't work. The array isn't circular anymore after that. The calculation of where to put the next item goes wrong. It depends on moving (when resizing) all data before the position of Front to after the position of Back. Other than reverting r40214 I see no easy solution for this. |
|
This issue is related to 0034420. |
|
Let's revert it then? |
|
Hello Bart, today my internet was down. I'm an experienced german Pascaler and my english is not so good, sorry! Maybe I have a solution. >>>>>>>>>>> procedure TDeque.IncreaseCapacity;inline; const // if size is small, multiply by 2; // if size bigger but <256M, inc by 1/8*size; // otherwise inc by 1/16*size cSizeSmall = 1*1024*1024; cSizeBig = 256*1024*1024; var i,OldEnd, DataSize:SizeUInt; begin OldEnd:=FCapacity; DataSize:=FCapacity*SizeOf(T); if FCapacity=0 then FCapacity:=4 else if DataSize<cSizeSmall then FCapacity:=FCapacity*2 else if DataSize<cSizeBig then FCapacity:=FCapacity+FCapacity div 8 else FCapacity:=FCapacity+FCapacity div 16; //============== NEW LINES ===================================================== if (FDataSize + FStart) > FCapacity then begin FCapacity:= FDataSize + FStart; end; //============================================================================== SetLength(FData, FCapacity); if (FStart>0) then for i:=0 to FStart-1 do begin FData[OldEnd+i]:=FData[i]; end; end; <<<<<<<<<<<<<<<<<< I noticed that the memory is not being released again. An example: 10000000 Push, 10000000 Pop, the memory for FData is used, but the size of the queue is 0. I think that's an other problem. |
|
The current implementation of Push/Pop only works (it designed this way) if all the items are continuous, that is if you walt throug the data array (FData in TDeque) when you start at "Front" and stop at the "Back" you maybe have to go from FData[High(FData)] to FData[0] (this is the circular aspect of the implementation), there can never be any "empty" (unassigned) value in between them. This then mens that when you increase the size of the array, you must make sure that this is the case again. Consider the following situation, where a TDeque for Chars has at some point 8 elements. ^ denotes the position of Front, * denotes the position of Back, a - denotes an unused value in the FData array. IJKLMNOH *^ As you can see, the Front item is 'H', the last is 'O', this works because after FData[7] your next Item is FData[0]. Now we increase the capacity when we add the next item: -------HIJKLMNOP ^ * You can see that Front still is 'H', Back is now the newly added item 'P'. The items are re-arragned in the array so that they are continuous again. Simply adding empy "slots" at the end will not do. Now imaging that you do not add 8 "slots" to the array, but just (for the sake of argument) 2. If you have to make the Items continuous again, this is possible, you get HIJKLMNOP-- ^ * But lets say that you start with HIJKLMNOP ^* Now it becomes rather difficult to rearrange (in a simple way) so that they are continuous again (after adding 2 empty "slots"). You could copy all the items to a new array in the right order and then copy tat array back to the original (and adjust FStart), but that would mean that the memory requirements of the TDeque would double. |
|
Hello Bart, the last zip file not correctly, sorry. I cannot imitate the behavior you described. On the contrary, the queue is worse than expected. The error does not occur in normal operation because the memory is always doubled. I have build a little example hows IncreaseCapacity added FData + 4. The mistake occurs earlier. With the Property Workarround you can enbable it. I have implementiert, that the memory relased if FDataSize=0, but it is uncommented. You can search it with '{$HINT !!!NEW!!!}' |
|
This algorithm for moving data in IncreaseCapacity seems to work. It will slow things down, but the memory doesn't grow with a factor 2 when dat gets huge. if (FStart>0) then begin if (FCapacity-OldEnd>=FStart) then //we have room to move all items in one go begin for i:=0 to FStart-1 do begin FData[OldEnd+i]:=FData[i]; //FData[i] := Default(T); end; end else begin //we have to move things around in chunks: we have more data in front of FStart then we have newly created unused elements CurLast := OldEnd-1; EmptyElems := High(FData)-CurLast; while (FStart>0) do begin Elems := Min(EmptyElems, FStart); //writeln(format('moving %d elements from 0 to %d',[elems,CurLast+1])); Move(FData[0], FData[CurLast+1], Elems*SizeOf(T)); FillChar(FData[0], Elems*SizeOf(T), 0); Inc(CurLast, Elems); //writeln(format('moving %d elements from %d to %d',[FCapacity-{FStart+}Elems, elems, 0])); Move(FData[Elems], FData[0], (FCapacity-Elems)*SizeOf(T)); Dec(FStart, Elems); Dec(CurLast, Elems); FillChar(FData[CurLast+1], Elems*SizeOf(T), 0); end; end; end; The original test case doesn't crash anymore with the above code. Question for the FPC devels: is the use of Move() safe here? |
|
If Move() is safe here, then the loop: for i:=0 to FStart-1 do can be replaced with Move(FData[0], FData[OldEnd], FStart*SizeOf(T)); |
|
When implementing generic queues myself some years ago, I ran into issues where if the generic type is reference-counted (string/dynamic array), doing a Move or FillChar like that will mess up the reference counts. Safer is to do repeated assignments or use Finalize(). |
|
I can see how FillChar messes up refcounts (it is only in the code now for debugging, so it's easier to see which parts of the FData are "occupied", it'l be either removed or ifdef-ed in the final version). Move in effect simply moves the entire memory to another location, so I don't really se how this messes up refcounts. My main concern about move is wether or not it takes alignment into account, or does it only work correctly if the array is packed. |
|
@Bart maybe read https://en.delphipraxis.net/topic/3348-generic-circular-buffer-library-released/ as it is about this issue: "TRingbuffer<T> unfortunately has several defects, mostly around simply using Move regardless the type of T (managed, containing weak reference)" seems your using the same data array so it could be fine but of FPC gets weak support some day your code is broken. Maybe better use IsManagedType and don't use move then? |
|
OK, so back to "lazy-copy" using a for loop. The link also suggests to set the source element to Default(T) after copying it to dest. One might introduce a protected virtual method: procedure TDeque.MoveData(StartIndex: SizeUInt; Offset: SizeInt; Count: SizeUInt); var i: SizeUInt; begin for i := 0 to Count-1 do begin FData[StartIndex+i+Offset] := FData[StartIndex+i]; FData[StartIndex+i] := Default(T); end; end; If you specialize TDeque wit a non-managed tye and you expect a large queue, then you could override MoveData to use system.move. Even for the original test it is noticeable. |
|
Possible patch attached. gdeque.increasecapacity.diff (2,252 bytes)
Index: packages/fcl-stl/src/gdeque.pp =================================================================== --- packages/fcl-stl/src/gdeque.pp (revision 48092) +++ packages/fcl-stl/src/gdeque.pp (working copy) @@ -30,7 +30,7 @@ procedure SetValue(position:SizeUInt; value:T);inline; function GetValue(position:SizeUInt):T;inline; function GetMutable(position:SizeUInt):PT;inline; - procedure IncreaseCapacity();inline; + procedure IncreaseCapacity(); public function Size():SizeUInt;inline; constructor Create(); @@ -142,7 +142,14 @@ GetMutable:=@FData[(FStart+position) mod FCapacity]; end; -procedure TDeque.IncreaseCapacity;inline; +procedure TDeque.IncreaseCapacity; + function Min(const A,B: SizeUInt): SizeUInt; inline; //no need to drag in the entire Math unit ;-) + begin + if (A<B) then + Result:=A + else + Result:=B; + end; const // if size is small, multiply by 2; // if size bigger but <256M, inc by 1/8*size; @@ -151,7 +158,7 @@ cSizeBig = 256*1024*1024; var i,OldEnd, - DataSize:SizeUInt; + DataSize,CurLast,EmptyElems,Elems:SizeUInt; begin OldEnd:=FCapacity; DataSize:=FCapacity*SizeOf(T); @@ -165,11 +172,29 @@ FCapacity:=FCapacity+FCapacity div 8 else FCapacity:=FCapacity+FCapacity div 16; - SetLength(FData, FCapacity); if (FStart>0) then - for i:=0 to FStart-1 do - FData[OldEnd+i]:=FData[i]; + begin + if (FCapacity-OldEnd>=FStart) then //we have room to move all items in one go + begin //don't use system.move: T might be a managed type that would need finilization + for i:=0 to FStart-1 do + FData[OldEnd+i]:=FData[i]; + end + else + begin //we have to move things around in chunks: we have more data in front of FStart than we have newly created unused elements + CurLast := OldEnd-1; + EmptyElems:=FCapacity-1-CurLast; + while (FStart>0) do + begin + Elems:=Min(EmptyElems, FStart); + for i:=0 to Elems-1 do + FData[CurLast+1+i]:=FData[i]; + for i := 0 to FCapacity-Elems-1 do + FData[i]:=FData[Elems+i]; + Dec(FStart, Elems); + end; + end; + end; end; procedure TDeque.Reserve(cap:SizeUInt);inline; |
|
Thanks applied. I modified it to take advantage of IsManagedType for using move for copying. |
|
If T is a managed type, then in the 2 for loops, it should finalize the data before overwriting it. Also in the "we have to move things around in chunks" part, Move() could be used if T is NOT managed. I would propose to introduce 2 new methods: MoveManageData(StartIndex: SizeUInt; Offset: SizeInt; NrElems: SizeUInt) and MoveData(StartIndex: SizeUInt; Offset: SizeInt; NrElems: SizeUInt); These can be called in both the loops depending on IsManagedType(T). If we make MoveData virtual, it would be easy to override it, so that it works with Objects (classes) too. That way it would be rather easy to derive a TObjDeque. Should I open a new bugreport for my proposed changes? |
|
Just add it here and re-open, saves probably time :) |
|
Since I'm not the original reporter of this bug, I cannot re-open this issue ;-) |
|
Yes, I realized this after pressing Add Note, this is why I set the report to "Feedback". |
|
I would propose somthing like this. It's mostly refactoring, but it should make it easier to derive a TObjectDeque. The FillChar's in MoveXXXData also make that you can move objects (classes actually) around, and still can free them without problems. It's not extensively tested yet, so don't commit. Please give feedback on the patch. gdeque.movedata.diff (3,297 bytes)
Index: packages/fcl-stl/src/gdeque.pp =================================================================== --- packages/fcl-stl/src/gdeque.pp (revision 48161) +++ packages/fcl-stl/src/gdeque.pp (working copy) @@ -31,6 +31,10 @@ function GetValue(position:SizeUInt):T;inline; function GetMutable(position:SizeUInt):PT;inline; procedure IncreaseCapacity(); + protected + procedure MoveSimpleData(StartIndex: SizeUInt; Offset: SizeInt; NrElems: SizeUInt); + procedure MoveManagedData(StartIndex: SizeUInt; Offset: SizeInt; NrElems: SizeUInt); + procedure MoveData(StartIndex: SizeUInt; Offset: SizeInt; NrElems: SizeUInt); public function Size():SizeUInt;inline; constructor Create(); @@ -127,6 +131,8 @@ procedure TDeque.SetValue(position:SizeUInt; value:T);inline; begin Assert(position < size, 'Deque access out of range'); + if IsManagedType(T) then + Finalize(FData[(FStart+position)mod FCapacity]); FData[(FStart+position)mod FCapacity]:=value; end; @@ -142,6 +148,40 @@ GetMutable:=@FData[(FStart+position) mod FCapacity]; end; + + +procedure TDeque.MoveSimpleData(StartIndex: SizeUInt; Offset: SizeInt; NrElems: SizeUInt); +begin + Move(FData[StartIndex], FData[StartIndex+Offset], NrElems*SizeOf(T)); + if Offset>0 then + FillChar(FData[StartIndex], NrElems*SizeOf(T), 0) + else + FillChar(FData[StartIndex+NrElems+Offset], -Offset*SizeOf(T), 0); +end; + +procedure TDeque.MoveManagedData(StartIndex: SizeUInt; Offset: SizeInt; NrElems: SizeUInt); +var + i: SizeUInt; +begin + //since we always move blocks where Abs(Offset)>=NrElems, there is no need for + //2 seperate loops (1 for ngeative and 1 for positive Offsett) + for i := 0 to NrElems-1 do + begin + Finalize(FData[StartIndex+i+Offset]); + FData[StartIndex+i+Offset] := FData[StartIndex+i]; + Finalize(FData[StartIndex+i]); + FillChar(FData[StartIndex+i], SizeOf(T), 0); + end; +end; + +procedure TDeque.MoveData(StartIndex: SizeUInt; Offset: SizeInt; NrElems: SizeUInt); +begin + if IsManagedType(T) then + MoveManagedData(StartIndex, Offset, NrElems) + else + MoveSimpleData(StartIndex, Offset, NrElems); +end; + procedure TDeque.IncreaseCapacity; function Min(const A,B: SizeUInt): SizeUInt; inline; //no need to drag in the entire Math unit ;-) begin @@ -177,11 +217,7 @@ begin if (FCapacity-OldEnd>=FStart) then //we have room to move all items in one go begin - if IsManagedType(T) then - for i:=0 to FStart-1 do - FData[OldEnd+i]:=FData[i] - else - Move(FData[0], FData[OldEnd], FStart*SizeOf(T)); + MoveData(0, OldEnd ,FStart) end else begin //we have to move things around in chunks: we have more data in front of FStart than we have newly created unused elements @@ -189,11 +225,9 @@ EmptyElems:=FCapacity-1-CurLast; while (FStart>0) do begin - Elems:=Min(EmptyElems, FStart); - for i:=0 to Elems-1 do - FData[CurLast+1+i]:=FData[i]; - for i := 0 to FCapacity-Elems-1 do - FData[i]:=FData[Elems+i]; + Elems := Min(EmptyElems, FStart); + MoveData(0, CurLast+1, Elems); + MoveData(Elems, -Elems, FCapacity-Elems); Dec(FStart, Elems); end; end; |
|
I think clear needs to finalize data as wel (if managed). And it must be made easy to override for a TObjectDeque (which possibly needs to free all data in this case). |
|
Checked and applied patch from Bart, thank you ! |
Date Modified | Username | Field | Change |
---|---|---|---|
2021-01-04 14:15 | Thomas Bulla | New Issue | |
2021-01-04 14:15 | Thomas Bulla | File Added: gqueue_test.lpr | |
2021-01-04 16:11 | Bart Broersma | Note Added: 0128072 | |
2021-01-04 16:26 | Bart Broersma | Note Edited: 0128072 | View Revisions |
2021-01-04 16:26 | Bart Broersma | Note Edited: 0128072 | View Revisions |
2021-01-04 16:27 | Bart Broersma | Note Edited: 0128072 | View Revisions |
2021-01-04 17:00 | Bart Broersma | Note Added: 0128076 | |
2021-01-04 17:01 | Bart Broersma | Note Edited: 0128076 | View Revisions |
2021-01-05 13:10 | Thomas Bulla | Note Added: 0128090 | |
2021-01-05 13:30 | Bart Broersma | Note Added: 0128091 | |
2021-01-05 14:12 | Thomas Bulla | Note Added: 0128092 | |
2021-01-05 23:22 | Bart Broersma | Note Added: 0128110 | |
2021-01-05 23:32 | Bart Broersma | Note Added: 0128112 | |
2021-01-05 23:40 | Bart Broersma | Note Edited: 0128112 | View Revisions |
2021-01-05 23:57 | Bart Broersma | Note Added: 0128114 | |
2021-01-06 12:52 | Bart Broersma | Note Added: 0128120 | |
2021-01-06 12:53 | Bart Broersma | Note Added: 0128121 | |
2021-01-06 16:18 | CudaText man | Note Added: 0128126 | |
2021-01-06 20:26 | Thomas Bulla | Note Added: 0128131 | |
2021-01-06 20:26 | Thomas Bulla | File Added: gqueue_test.zip | |
2021-01-06 21:06 | Sven Barth | Relationship added | child of 0034420 |
2021-01-06 23:51 | Bart Broersma | Note Added: 0128134 | |
2021-01-07 16:02 | Thomas Bulla | Note Added: 0128141 | |
2021-01-07 16:02 | Thomas Bulla | File Added: gqueue_test_2.zip | |
2021-01-08 19:45 | Bart Broersma | Note Added: 0128175 | |
2021-01-08 19:47 | Bart Broersma | Note Edited: 0128175 | View Revisions |
2021-01-08 23:55 | Bart Broersma | Note Added: 0128178 | |
2021-01-09 02:22 | Colin Haywood | Note Added: 0128183 | |
2021-01-09 11:54 | Bart Broersma | Note Added: 0128194 | |
2021-01-09 16:26 | ravi dion | Note Added: 0128201 | |
2021-01-09 18:37 | Bart Broersma | Note Added: 0128209 | |
2021-01-09 18:37 | Bart Broersma | Note Edited: 0128209 | View Revisions |
2021-01-11 23:06 | Bart Broersma | Note Added: 0128275 | |
2021-01-11 23:06 | Bart Broersma | File Added: gdeque.increasecapacity.diff | |
2021-01-13 22:25 | Florian | Assigned To | => Florian |
2021-01-13 22:25 | Florian | Status | new => resolved |
2021-01-13 22:25 | Florian | Resolution | open => fixed |
2021-01-13 22:25 | Florian | Fixed in Version | => 3.3.1 |
2021-01-13 22:25 | Florian | Fixed in Revision | => 48154 |
2021-01-13 22:25 | Florian | FPCTarget | => - |
2021-01-13 22:25 | Florian | Note Added: 0128304 | |
2021-01-13 23:31 | Bart Broersma | Note Added: 0128305 | |
2021-01-13 23:38 | Bart Broersma | Note Edited: 0128305 | View Revisions |
2021-01-14 18:50 | Florian | Note Added: 0128321 | |
2021-01-14 18:50 | Florian | Assigned To | Florian => |
2021-01-14 18:50 | Florian | Status | resolved => feedback |
2021-01-14 18:50 | Florian | Resolution | fixed => open |
2021-01-14 20:50 | Bart Broersma | Note Added: 0128322 | |
2021-01-14 21:28 | Florian | Note Added: 0128325 | |
2021-01-15 19:20 | Bart Broersma | Note Added: 0128362 | |
2021-01-15 19:20 | Bart Broersma | File Added: gdeque.movedata.diff | |
2021-01-15 21:23 | Bart Broersma | Note Added: 0128365 | |
2021-01-24 18:41 | Michael Van Canneyt | Assigned To | => Michael Van Canneyt |
2021-01-24 18:41 | Michael Van Canneyt | Status | feedback => resolved |
2021-01-24 18:41 | Michael Van Canneyt | Resolution | open => fixed |
2021-01-24 18:41 | Michael Van Canneyt | Fixed in Revision | 48154 => 48405 |
2021-01-24 18:41 | Michael Van Canneyt | Note Added: 0128566 |