Skip to contentSkip to conversation

data loss in 2.6.9-rc1-mm1

DISCUSSION Gergely Tamas 11 participants 26 messages Aug 27, 2004 – Aug 31, 2004

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

Hi!

I've hit the following data loss problem under 2.6.9-rc1-mm1.

If I copy data from a file to another the target will be smaller then the source file.

2.6.9-rc1 does not have this problem
2.6.8.1-mm4 does not have this problem
2.6.9-rc1-mm1 _does have_ this problem

I tried this with reiserfs and xfs and it happened with both of them.

See the testcase at the bottom of this mail.

Thanks in advance,
Gergely

--------------------------------------------------

$ uname -r
2.6.9-rc1
$ dd if=/dev/zero of=testfile bs=$((1024*1024)) count=10
10+0 records in
10+0 records out
10485760 bytes transferred in 0.028646 seconds (366045254 bytes/sec)
$ du -sb testfile
10485760        testfile
$ cat testfile > testfile.1
$ du -sb testfile.1
10485760        testfile.1

--------------------------------------------------

$ uname -r
2.6.8.1-mm4
$ dd if=/dev/zero of=testfile bs=$((1024*1024)) count=10
10+0 records in
10+0 records out
10485760 bytes transferred in 0.028632 seconds (366226397 bytes/sec)
$ du -sb testfile
10485760        testfile
$ cat testfile > testfile.1
$ du -sb testfile.1
10485760        testfile.1

--------------------------------------------------

$ uname -r
2.6.9-rc1-mm1
$ dd if=/dev/zero of=testfile bs=$((1024*1024)) count=10
10+0 records in
10+0 records out
10485760 bytes transferred in 0.028418 seconds (368981986 bytes/sec)
$ du -sb testfile
10485760        testfile
$ cat testfile > testfile.1
$ du -sb testfile.1
10481664        testfile.1
#1 lore raw
Quoted 4 lines · Gergely Tamas

I've hit the following data loss problem under 2.6.9-rc1-mm1.

If I copy data from a file to another the target will be smaller then the source file.

[snip]

Quoted 8 lines
$ uname -r
2.6.9-rc1-mm1
$ dd if=/dev/zero of=testfile bs=$((1024*1024)) count=10
10+0 records in
10+0 records out
10485760 bytes transferred in 0.028418 seconds (368981986 bytes/sec)
Quoted 7 lines
$ du -sb testfile
10485760        testfile
$ cat testfile > testfile.1
$ du -sb testfile.1
10481664        testfile.1

The difference is exactly 4096 bytes, i.e. 1 whole page. Seems like an off-by-one error somewhere in the file access or page cache code.

It would be interesting to know whether the read is truncated or whether the write is truncated. So could you tell us what is returned by:

cat testfile | wc -c

Also your .config would probably be helpful.

Best regards,

Anton

Signature · 4 lines
Anton Altaparmakov <aia21 at cam.ac.uk> (replace at with @)
Unix Support, Computing Service, University of Cambridge, CB2 3QH, UK
Linux NTFS maintainer / IRC: #ntfs on irc.freenode.net
WWW: http://linux-ntfs.sf.net/, http://www-stu.christs.cam.ac.uk/~aia21/
#2 lore raw

Hi!

Quoted 7 lines

The difference is exactly 4096 bytes, i.e. 1 whole page. Seems like an off-by-one error somewhere in the file access or page cache code.

It would be interesting to know whether the read is truncated or whether the write is truncated. So could you tell us what is returned by:

cat testfile | wc -c

$ cat testfile | wc -c
10481664
Quoted 1 line

Also your .config would probably be helpful.

[ http://dice.mfa.kfki.hu/dot.config-2.6.9-rc1-mm1.gz ]

Thanks,
Gergely

#3 lore raw
In reply to Anton Altaparmakov

Alle 13:05, venerdì 27 agosto 2004, Anton Altaparmakov ha scritto:

Quoted 6 lines

The difference is exactly 4096 bytes, i.e. 1 whole page. Seems like an off-by-one error somewhere in the file access or page cache code.

It would be interesting to know whether the read is truncated or whether the write is truncated.

I can confirm very bad behaviour on my machine running 2.6.9-rc1-mm1

I've noticed curruption of rpm database, (symptom: error: db4 error(-30989) from dbcursor->c_get: DB_PAGE_NOTFOUND: Requested page not found )

So I've tried to rebuild the database, failing with the same error.

I've completely wiped out the database direcory, copied another one from a similar machine (just for testing). Now under 2.6.7-mm7 all database is read just fine, but under 2.6.9-rc1-mm1 i get the same error DB_PAGE_NOTFOUND, so I suspect read error.
If I try to tar the archive, I get several errors, i.e.:
tar: /var/lib/rpm/Packages: File shrank by 4096 bytes; padding with zeros (2.6.9-rc1-mm1)

Filesystem: jfs
system: P IV 1.70
Attached you can find my config file for 2.6.9-rc1-mm1.

I can make tests if needed.

Signature · 4 lines
Fabio "Cova" Coatti    http://members.ferrara.linux.it/cova     
Ferrara Linux Users Group           http://ferrara.linux.it
GnuPG fp:9765 A5B6 6843 17BC A646  BE8C FA56 373A 5374 C703
Old SysOps never die... they simply forget their password.
#4 lore raw
In reply to Gergely Tamas
Quoted 8 lines
$ uname -r
2.6.9-rc1-mm1
$ dd if=/dev/zero of=testfile bs=$((1024*1024)) count=10
10+0 records in
10+0 records out
10485760 bytes transferred in 0.028418 seconds (368981986 bytes/sec)
Quoted 7 lines
$ du -sb testfile
10485760        testfile
$ cat testfile > testfile.1
$ du -sb testfile.1
10481664        testfile.1

What does ls -l testfile.1 give?
What you describe actually can be correct behaviour, since the file is all zeros.

Although yes, it seems highly improbable someone implemented an optimization that cuts away just one of 2560 pages.

Tim

#5 lore raw

Hi!

Quoted 1 line

What does ls -l testfile.1 give?

$ ls -l testfile{,.1}
-rw-r--r--  1 dice users 10485760 Aug 27 13:25 testfile
-rw-r--r--  1 dice users 10481664 Aug 27 13:25 testfile.1
Quoted 2 lines

What you describe actually can be correct behaviour, since the file is all zeros.

Same test with other source file...

$ dd if=linux-2.6.8.1.tar.bz2 of=testfile bs=$((1024*1024)) count=10
10+0 records in
10+0 records out
10485760 bytes transferred in 0.061916 seconds (169354917 bytes/sec)
$ du -sb testfile
10485760        testfile
$ cat testfile > testfile.1
$ du -sb testfile.1
10481664        testfile.1
Quoted 2 lines

Although yes, it seems highly improbable someone implemented an optimization that cuts away just one of 2560 pages.

Thanks,
Gergely

#6 lore raw
In reply to Gergely Tamas
Quoted 4 lines · Gergely Tamas

Hi!

I've hit the following data loss problem under 2.6.9-rc1-mm1.

Quoted 8 lines

If I copy data from a file to another the target will be smaller then the source file.

2.6.9-rc1 does not have this problem
2.6.8.1-mm4 does not have this problem
2.6.9-rc1-mm1 _does have_ this problem

I tried this with reiserfs and xfs and it happened with both of them.

I also saw weird errors when I (very briefly) ran 2.6.9-rc1-mm1 on my News server.
- scp of a newly created file to another box failed with an I/O error
- md5sums of newly created files were incorrect
- NFS mount request to this box failed with permission denied

I don't have time to investigate further, but I've experienced no problems with either 2.6.9-rc1 or 2.6.8.1-mm4. All local file systems were ext3, btw.

/Mikael

#7 lore raw
In reply to Gergely Tamas
Quoted 10 lines · Gergely Tamas

Hi!

I've hit the following data loss problem under 2.6.9-rc1-mm1.

If I copy data from a file to another the target will be smaller then the source file.

2.6.9-rc1 does not have this problem
2.6.8.1-mm4 does not have this problem
2.6.9-rc1-mm1 _does have_ this problem

I've seen some errors from KDE too. Let me do your test...

# dd if=/dev/zero of=testfile bs=$((1024*1024)) count=10
10+0 records in
10+0 records out
# cat testfile > testfile.1
# ls -l test*
-rw-r--r--    1 root     root     10485760 Aug 27 14:53 testfile
-rw-r--r--    1 root     root     10481664 Aug 27 14:53 testfile.1
Signature · 1 line
vda
#8 lore raw
Quoted 18 lines · Denis Vlasenko
Quoted 9 lines · Gergely Tamas

I've hit the following data loss problem under 2.6.9-rc1-mm1.

If I copy data from a file to another the target will be smaller then the source file.

2.6.9-rc1 does not have this problem
2.6.8.1-mm4 does not have this problem
2.6.9-rc1-mm1 _does have_ this problem

I've seen some errors from KDE too. Let me do your test...

# dd if=/dev/zero of=testfile bs=$((1024*1024)) count=10
10+0 records in
10+0 records out
# cat testfile > testfile.1
# ls -l test*

Review

Quoted 2 lines
(quoted diff) +0 -2
-rw-r--r--    1 root     root     10485760 Aug 27 14:53 testfile
-rw-r--r--    1 root     root     10481664 Aug 27 14:53 testfile.1

Hmm, 2.6.9-rc1-mm1 looks like not a release to trust your (page size multiple) data to! You should find the patch below fixes it (and, I hope, the issue the erroneous patches were trying to fix).

mm/filemap.c +8 -6
@@ -722,10 +722,7 @@ void do_generic_mapping_read(struct addr
 	offset = *ppos & ~PAGE_CACHE_MASK;
 
 	isize = i_size_read(inode);
-	if (!isize)
-		goto out;
-
-	end_index = (isize - 1) >> PAGE_CACHE_SHIFT;
+	end_index = isize >> PAGE_CACHE_SHIFT;
 
 	for (;;) {
 		struct page *page;
@@ -733,6 +730,11 @@ void do_generic_mapping_read(struct addr
 
 		if (index > end_index)
 			goto out;
+		if (index == end_index) {
+			nr = isize & ~PAGE_CACHE_MASK;
+			if (nr <= offset)
+				goto out;
+		}
 
 		cond_resched();
 		page_cache_readahead(mapping, &ra, filp, index);
@@ -831,8 +833,8 @@ readpage:
 		 * another truncate extends the file - this is desired though).
 		 */
 		isize = i_size_read(inode);
-		end_index = (isize - 1) >> PAGE_CACHE_SHIFT;
-		if (unlikely(!isize || index > end_index)) {
+		end_index = isize >> PAGE_CACHE_SHIFT;
+		if (unlikely(index > end_index)) {
 			page_cache_release(page);
 			goto out;
 		}
#9 lore raw
Quoted 5 lines

Hmm, 2.6.9-rc1-mm1 looks like not a release to trust your (page size multiple) data to! You should find the patch below fixes it (and, I hope, the issue the erroneous patches were trying to fix).

$ dd if=/dev/zero of=testfile bs=$((1024*1024)) count=10
10+0 records in
10+0 records out
10485760 bytes transferred in 0.028393 seconds (369307252 bytes/sec)
$ du -sb testfile
10485760        testfile
$ cat testfile > testfile.1
$ du -sb testfile.1
10485760        testfile.1

Seems to be working fine now, thanks.
Gergely

#10 lore raw

Alle 16:18, venerdì 27 agosto 2004, Gergely Tamas ha scritto:

Quoted 5 lines
Quoted 5 lines

Hmm, 2.6.9-rc1-mm1 looks like not a release to trust your (page size multiple) data to! You should find the patch below fixes it (and, I hope, the issue the erroneous patches were trying to fix).

JFYI: It seems that this patch fixes my problem with rpm database as well; thanks a lot.

Signature · 4 lines
Fabio "Cova" Coatti    http://members.ferrara.linux.it/cova     
Ferrara Linux Users Group           http://ferrara.linux.it
GnuPG fp:9765 A5B6 6843 17BC A646  BE8C FA56 373A 5374 C703
Old SysOps never die... they simply forget their password.
#11 lore raw
In reply to Hugh Dickins
Quoted 14 lines · Hugh Dickins
Quoted 13 lines · Denis Vlasenko
Quoted 9 lines · Gergely Tamas

I've hit the following data loss problem under 2.6.9-rc1-mm1.

If I copy data from a file to another the target will be smaller then the source file.

2.6.9-rc1 does not have this problem
2.6.8.1-mm4 does not have this problem
2.6.9-rc1-mm1 _does have_ this problem

I've seen some errors from KDE too. Let me do your test...

Quoted 11 lines

Review

Quoted 7 lines
# dd if=/dev/zero of=testfile bs=$((1024*1024)) count=10
10+0 records in
10+0 records out
# cat testfile > testfile.1
# ls -l test*
(quoted diff) +0 -2
-rw-r--r--    1 root     root     10485760 Aug 27 14:53 testfile
-rw-r--r--    1 root     root     10481664 Aug 27 14:53 testfile.1

Hmm, 2.6.9-rc1-mm1 looks like not a release to trust your (page size multiple) data to! You should find the patch below fixes it (and, I hope, the issue the erroneous patches were trying to fix).

Hmm.. now I fail to understand how this code works.

assuming page size is 4096, if the size of the file is 4096, is the end_index 0 or is it 1?
I had this assumption:

	file size in bytes			end_index
	-----------------			---------
		1 to 4096			0
		4097 to 2*4096			1
		2*4096+1 to 3*4096		2
		...				..

or is the isize value reported by i_size_read(inode) one less than the size of the real file?

What am I missing?
RP

Quoted 41 lines
mm/filemap.c +8 -6
@@ -722,10 +722,7 @@ void do_generic_mapping_read(struct addr
 	offset = *ppos & ~PAGE_CACHE_MASK;
 
 	isize = i_size_read(inode);
-	if (!isize)
-		goto out;
-
-	end_index = (isize - 1) >> PAGE_CACHE_SHIFT;
+	end_index = isize >> PAGE_CACHE_SHIFT;
 
 	for (;;) {
 		struct page *page;
@@ -733,6 +730,11 @@ void do_generic_mapping_read(struct addr
 
 		if (index > end_index)
 			goto out;
+		if (index == end_index) {
+			nr = isize & ~PAGE_CACHE_MASK;
+			if (nr <= offset)
+				goto out;
+		}
 
 		cond_resched();
 		page_cache_readahead(mapping, &ra, filp, index);
@@ -831,8 +833,8 @@ readpage:
 		 * another truncate extends the file - this is desired though).
 		 */
 		isize = i_size_read(inode);
-		end_index = (isize - 1) >> PAGE_CACHE_SHIFT;
-		if (unlikely(!isize || index > end_index)) {
+		end_index = isize >> PAGE_CACHE_SHIFT;
+		if (unlikely(index > end_index)) {
 			page_cache_release(page);
 			goto out;
 		}
#12 lore raw
Quoted 10 lines · Ram Pai
Quoted 4 lines · Hugh Dickins

Hmm, 2.6.9-rc1-mm1 looks like not a release to trust your (page size multiple) data to! You should find the patch below fixes it (and, I hope, the issue the erroneous patches were trying to fix).

Hmm.. now I fail to understand how this code works.

assuming page size is 4096, if the size of the file is 4096, is the end_index 0 or is it 1?

Before your change and after mine, 1; with your change, 0.

Quoted 8 lines

I had this assumption:

	file size in bytes			end_index
	-----------------			---------
		1 to 4096			0
		4097 to 2*4096			1
		2*4096+1 to 3*4096		2
		...				..

Well, that's what you changed it to, when you patched from the original

		end_index = isize >> PAGE_CACHE_SHIFT;

to end_index = (isize - 1) >> PAGE_CACHE_SHIFT;

But the "nr <= offset" check(s) relies on the original convention:

		0 to 4095			0
		4096 to 8191			1
		...				..
Quoted 2 lines

or is the isize value reported by i_size_read(inode) one less than the size of the real file?

No!

Quoted 1 line

What am I missing?

You're expecting end_index to be the index of the last (possibly incomplete) page of the file. And that might be a reasonable way of working it (though the special case of an empty file hints not). But the nr,offset checks (I say checks because I added another like the one further down, hopefully to fix the extra readahead issue) require the original convention. Just try it out with numbers.

Hugh

#13 lore raw
Quoted 37 lines · Hugh Dickins
Quoted 10 lines · Ram Pai
Quoted 4 lines · Hugh Dickins

Hmm, 2.6.9-rc1-mm1 looks like not a release to trust your (page size multiple) data to! You should find the patch below fixes it (and, I hope, the issue the erroneous patches were trying to fix).

Hmm.. now I fail to understand how this code works.

assuming page size is 4096, if the size of the file is 4096, is the end_index 0 or is it 1?

Before your change and after mine, 1; with your change, 0.

Quoted 8 lines

I had this assumption:

	file size in bytes			end_index
	-----------------			---------
		1 to 4096			0
		4097 to 2*4096			1
		2*4096+1 to 3*4096		2
		...				..

Well, that's what you changed it to, when you patched from the original

		end_index = isize >> PAGE_CACHE_SHIFT;

to end_index = (isize - 1) >> PAGE_CACHE_SHIFT;

But the "nr <= offset" check(s) relies on the original convention:

		0 to 4095			0
		4096 to 8191			1
		...				..
Quoted 2 lines

or is the isize value reported by i_size_read(inode) one less than the size of the real file?

No!

Quoted 8 lines
Quoted 1 line

What am I missing?

You're expecting end_index to be the index of the last (possibly incomplete) page of the file. And that might be a reasonable way of working it (though the special case of an empty file hints not). But the nr,offset checks (I say checks because I added another like the one further down, hopefully to fix the extra readahead issue) require the original convention. Just try it out with numbers.

got it! Everything got changed to the new convention except that the calculation of 'nr' just before the check "nr <= offset" .

I have generated this patch which takes care of that and hence fixes the data loss problem as well. I guess it is cleaner too.

This patch is generated w.r.t 2.6.8.1. If everybody blesses this patch I will forward it to Andrew.

RP

Quoted 3 lines

Hugh

mm/filemap.c renamed from linux-2.6.8.1/mm/filemap.c +10 -5
@@ -665,14 +665,18 @@ void do_generic_mapping_read(struct addr
 	offset = *ppos & ~PAGE_CACHE_MASK;
 
 	isize = i_size_read(inode);
-	end_index = isize >> PAGE_CACHE_SHIFT;
-	if (index > end_index)
+	if (!isize)
 		goto out;
 
+	end_index = (isize - 1) >> PAGE_CACHE_SHIFT;
+
 	for (;;) {
 		struct page *page;
 		unsigned long nr, ret;
 
+		if (index > end_index)
+			goto out;
+
 		cond_resched();
 		page_cache_readahead(mapping, &ra, filp, index);
 
@@ -688,7 +692,8 @@ page_ok:
 		/* nr is the maximum number of bytes to copy from this page */
 		nr = PAGE_CACHE_SIZE;
 		if (index == end_index) {
-			nr = isize & ~PAGE_CACHE_MASK;
+			BUG_ON(isize==0);
+			nr = ((isize - 1) & ~PAGE_CACHE_MASK) + 1;
 			if (nr <= offset) {
 				page_cache_release(page);
 				goto out;
@@ -770,8 +775,8 @@ readpage:
 		 * another truncate extends the file - this is desired though).
 		 */
 		isize = i_size_read(inode);
-		end_index = isize >> PAGE_CACHE_SHIFT;
-		if (index > end_index) {
+		end_index = (isize - 1) >> PAGE_CACHE_SHIFT;
+		if (unlikely(!isize || index > end_index)) {
 			page_cache_release(page);
 			goto out;
 		}
#14 lore raw
Quoted 9 lines · Ram Pai

got it! Everything got changed to the new convention except that the calculation of 'nr' just before the check "nr <= offset" .

I have generated this patch which takes care of that and hence fixes the data loss problem as well. I guess it is cleaner too.

This patch is generated w.r.t 2.6.8.1. If everybody blesses this patch I will forward it to Andrew.

It looks like it should be OK... but at what point does it become simpler to use my patch which just moves the original calculation up, and does it again if we have to ->readpage()?

(assuming you agree that it solves the problem)

---

 linux-2.6-npiggin/mm/filemap.c |   29 ++++++++++++++++++-----------
 1 files changed, 18 insertions(+), 11 deletions(-)
mm/filemap.c renamed from mm/filemap.c~mm-gmr-fix +18 -11
diff -puN mm/filemap.c~mm-gmr-fix mm/filemap.c
@@ -724,6 +724,15 @@ void do_generic_mapping_read(struct addr
 		struct page *page;
 		unsigned long nr, ret;
 
+		/* nr is the maximum number of bytes to copy from this page */
+		nr = PAGE_CACHE_SIZE;
+		if (index == end_index) {
+			nr = isize & ~PAGE_CACHE_MASK;
+			if (nr <= offset)
+				goto out;
+		}
+		nr = nr - offset;
+
 		cond_resched();
 		page_cache_readahead(mapping, &ra, filp, index);
 
@@ -736,17 +745,6 @@ find_page:
 		if (!PageUptodate(page))
 			goto page_not_up_to_date;
 page_ok:
-		/* nr is the maximum number of bytes to copy from this page */
-		nr = PAGE_CACHE_SIZE;
-		if (index == end_index) {
-			nr = isize & ~PAGE_CACHE_MASK;
-			if (nr <= offset) {
-				page_cache_release(page);
-				goto out;
-			}
-		}
-		nr = nr - offset;
-
 		/* If users can be writing to this page using arbitrary
 		 * virtual addresses, take care about potential aliasing
 		 * before reading the page on the kernel side.
@@ -826,6 +824,15 @@ readpage:
 			page_cache_release(page);
 			goto out;
 		}
+		nr = PAGE_CACHE_SIZE;
+		if (index == end_index) {
+			nr = isize & ~PAGE_CACHE_MASK;
+			if (nr <= offset) {
+				page_cache_release(page);
+				goto out;
+			}
+		}
+		nr = nr - offset;
 		goto page_ok;
 
 readpage_error:

_

#15 lore raw
Quoted 8 lines · Nick Piggin
Quoted 7 lines · Ram Pai

got it! Everything got changed to the new convention except that the calculation of 'nr' just before the check "nr <= offset" .

I have generated this patch which takes care of that and hence fixes the data loss problem as well. I guess it is cleaner too.

Quoted 8 lines
Quoted 2 lines

This patch is generated w.r.t 2.6.8.1. If everybody blesses this patch I will forward it to Andrew.

It looks like it should be OK... but at what point does it become simpler to use my patch which just moves the original calculation up, and does it again if we have to ->readpage()?

(assuming you agree that it solves the problem)

I agree your patch also solves the problem.

Either way is fine. Even Hugh's patch almost does the same thing as yours. The only advantage with my page is it does the calculation in only one place and does not repeat it. Also I feel its more intuitive to assume that index 0 covers range 0 to 4095 i.e index n covers range n*PAGE_SIZE to ((n+1)*PAGE_SIZE)-1. Currently the code assumes index 0 covers range 1 to 4096 i.e index n covers range (n*PAGE_SIZE)+1 to (n+1)*PAGE_SIZE.

this is the 4th time we are trying to nail down the same thing. We better get it right this time. So any correct patch is ok with me.

RP

Quoted 65 lines

______________________________________________________________________

---

 linux-2.6-npiggin/mm/filemap.c |   29 ++++++++++++++++++-----------
 1 files changed, 18 insertions(+), 11 deletions(-)

diff -puN mm/filemap.c~mm-gmr-fix mm/filemap.c

mm/filemap.c renamed from mm/filemap.c~mm-gmr-fix +18 -11
@@ -724,6 +724,15 @@ void do_generic_mapping_read(struct addr
 		struct page *page;
 		unsigned long nr, ret;
 
+		/* nr is the maximum number of bytes to copy from this page */
+		nr = PAGE_CACHE_SIZE;
+		if (index == end_index) {
+			nr = isize & ~PAGE_CACHE_MASK;
+			if (nr <= offset)
+				goto out;
+		}
+		nr = nr - offset;
+
 		cond_resched();
 		page_cache_readahead(mapping, &ra, filp, index);
 
@@ -736,17 +745,6 @@ find_page:
 		if (!PageUptodate(page))
 			goto page_not_up_to_date;
 page_ok:
-		/* nr is the maximum number of bytes to copy from this page */
-		nr = PAGE_CACHE_SIZE;
-		if (index == end_index) {
-			nr = isize & ~PAGE_CACHE_MASK;
-			if (nr <= offset) {
-				page_cache_release(page);
-				goto out;
-			}
-		}
-		nr = nr - offset;
-
 		/* If users can be writing to this page using arbitrary
 		 * virtual addresses, take care about potential aliasing
 		 * before reading the page on the kernel side.
@@ -826,6 +824,15 @@ readpage:
 			page_cache_release(page);
 			goto out;
 		}
+		nr = PAGE_CACHE_SIZE;
+		if (index == end_index) {
+			nr = isize & ~PAGE_CACHE_MASK;
+			if (nr <= offset) {
+				page_cache_release(page);
+				goto out;
+			}
+		}
+		nr = nr - offset;
 		goto page_ok;
 
 readpage_error:

_

#16 lore raw
Quoted 3 lines · Ram Pai

this is the 4th time we are trying to nail down the same thing. We
better get it right this time. So any correct patch is ok with me.

There's no rush and it's a kernel fastpath. Let's get the optimal code debugged and merged, please.

#17 lore raw
In reply to Ram Pai
Quoted 17 lines · Ram Pai
Quoted 15 lines · Nick Piggin
Quoted 8 lines · Ram Pai

got it! Everything got changed to the new convention except that the calculation of 'nr' just before the check "nr <= offset" .

I have generated this patch which takes care of that and hence fixes the data loss problem as well. I guess it is cleaner too.

This patch is generated w.r.t 2.6.8.1. If everybody blesses this patch I will forward it to Andrew.

It looks like it should be OK... but at what point does it become simpler to use my patch which just moves the original calculation up, and does it again if we have to ->readpage()?

Quoted 7 lines
Quoted 1 line

(assuming you agree that it solves the problem)

I agree your patch also solves the problem.

Either way is fine. Even Hugh's patch almost does the same thing as yours.

Ahh, yep - Hugh just forgot to also move the "nr" calculation into the ->readpage path, so it hits twice on the fast path.

Quoted 2 lines

The only advantage with my page is it does the calculation in only one place and does not repeat it. Also I feel its more intuitive to

Well kind of - but you are having to jump through hoops to get there. Yours does the following checks:

	/* fast path, read nr_pages from pagecache */
	if (!isize)
		goto out;
	for (i = 0; i < nr_pages; i++) {
		if (index > end_index)
			goto out;
		if (index == end_index) {
			nr = ((isize - 1) & ~PAGE_CACHE_MASK) + 1;
			if (nr <= offset) {
				page_cache_release(page);
				goto out;
			}
		}

		/* slowpath, ->readpage */
		if (unlikely(!isize || index > end_index)) {
			page_cache_release(page);
			goto out;
		}
	}

Mine does:

	if (index > end_index)
		goto out;
	for (i = 0; i < pages_to_read; i++) {
		if (index == end_index) {
			nr = isize & ~PAGE_CACHE_MASK;
			if (nr <= offset)
				goto out;
		}

		/* slowpath, ->readpage */
		if (index > end_index) {
			page_cache_release(page);
			goto out;
		}
		if (index == end_index) {
			nr = isize & ~PAGE_CACHE_MASK;
			if (nr <= offset) {
				page_cache_release(page);
				goto out;
			}
		}
	}

So my fastpath is surely leaner, while the slowpath isn't a clear loser.

What's more, it looks like mine handles the corner case of reading off the end of a non-PAGE_SIZE file (but within the same page). I think yours will drop through and do the ->readpage, while mine doesn't...?

Quoted 5 lines

assume that index 0 covers range 0 to 4095 i.e index n covers range n*PAGE_SIZE to ((n+1)*PAGE_SIZE)-1. Currently the code assumes index 0 covers range 1 to 4096 i.e index n covers range (n*PAGE_SIZE)+1 to (n+1)*PAGE_SIZE.

It is definitely a pretty ugly function all round. I like the 0-4095 thing better too, but my counter argument to that is that this is the minimal change, and similar to how it has previously worked.

Quoted 3 lines

this is the 4th time we are trying to nail down the same thing. We better get it right this time. So any correct patch is ok with me.

I agree. We'll leave it to someone else to decide, then ;)

#18 lore raw

Well, guys, to make it 100% clear: if I apply the Nick's patch to the 2.6.9-rc1-mm1 tree, it will fix the data loss issue. Is that right?

RJW

Quoted 102 lines · Nick Piggin
Quoted 15 lines · Ram Pai
Quoted 14 lines · Nick Piggin
Quoted 8 lines · Ram Pai

got it! Everything got changed to the new convention except that the calculation of 'nr' just before the check "nr <= offset" .

I have generated this patch which takes care of that and hence fixes the data loss problem as well. I guess it is cleaner too.

This patch is generated w.r.t 2.6.8.1. If everybody blesses this patch I will forward it to Andrew.

It looks like it should be OK... but at what point does it become simpler to use my patch which just moves the original calculation up, and does it again if we have to ->readpage()?

Quoted 6 lines
Quoted 1 line

(assuming you agree that it solves the problem)

I agree your patch also solves the problem.

Either way is fine. Even Hugh's patch almost does the same thing as yours.

Ahh, yep - Hugh just forgot to also move the "nr" calculation into the ->readpage path, so it hits twice on the fast path.

Quoted 2 lines

The only advantage with my page is it does the calculation in only one place and does not repeat it. Also I feel its more intuitive to

Well kind of - but you are having to jump through hoops to get there. Yours does the following checks:

	/* fast path, read nr_pages from pagecache */
	if (!isize)
		goto out;
	for (i = 0; i < nr_pages; i++) {
		if (index > end_index)
			goto out;
		if (index == end_index) {
			nr = ((isize - 1) & ~PAGE_CACHE_MASK) + 1;
			if (nr <= offset) {
				page_cache_release(page);
				goto out;
			}
		}

		/* slowpath, ->readpage */
		if (unlikely(!isize || index > end_index)) {
			page_cache_release(page);
			goto out;
		}
	}

Mine does:

	if (index > end_index)
		goto out;
	for (i = 0; i < pages_to_read; i++) {
		if (index == end_index) {
			nr = isize & ~PAGE_CACHE_MASK;
			if (nr <= offset)
				goto out;
		}

		/* slowpath, ->readpage */
		if (index > end_index) {
			page_cache_release(page);
			goto out;
		}
		if (index == end_index) {
			nr = isize & ~PAGE_CACHE_MASK;
			if (nr <= offset) {
				page_cache_release(page);
				goto out;
			}
		}
	}

So my fastpath is surely leaner, while the slowpath isn't a clear loser.

What's more, it looks like mine handles the corner case of reading off the end of a non-PAGE_SIZE file (but within the same page). I think yours will drop through and do the ->readpage, while mine doesn't...?

Quoted 4 lines

assume that index 0 covers range 0 to 4095 i.e index n covers range n*PAGE_SIZE to ((n+1)*PAGE_SIZE)-1. Currently the code assumes index 0 covers range 1 to 4096 i.e index n covers range (n*PAGE_SIZE)+1 to (n+1)*PAGE_SIZE.

It is definitely a pretty ugly function all round. I like the 0-4095 thing better too, but my counter argument to that is that this is the minimal change, and similar to how it has previously worked.

Quoted 2 lines

this is the 4th time we are trying to nail down the same thing. We better get it right this time. So any correct patch is ok with me.

I agree. We'll leave it to someone else to decide, then ;)
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/

Signature · 3 lines
For a successful technology, reality must take precedence over public 
relations, for nature cannot be fooled.
					-- Richard P. Feynman
#19 lore raw
Quoted 3 lines · Rafael J. Wysocki

Well, guys, to make it 100% clear: if I apply the Nick's patch to the
2.6.9-rc1-mm1 tree, it will fix the data loss issue. Is that right?

Should do. Or revert

ftp://ftp.kernel.org/pub/linux/kernel/people/akpm/patches/2.6/2.6.9-rc1/2.6.9-rc1-mm1/broken-out/re-fix-pagecache-reading-off-by-one-cleanup.patch

and then

ftp://ftp.kernel.org/pub/linux/kernel/people/akpm/patches/2.6/2.6.9-rc1/2.6.9-rc1-mm1/broken-out/re-fix-pagecache-reading-off-by-one.patch

#20 lore raw
Quoted 6 lines · Andrew Morton
Quoted 1 line · Rafael J. Wysocki

Well, guys, to make it 100% clear: if I apply the Nick's patch to the 2.6.9-rc1-mm1 tree, it will fix the data loss issue. Is that right?

Should do.

It passes test cases that would previously fail here, so consider it lightly tested. Note that the patch is on top of 2.6.9-rc1 though, it becomes slightly deranged when applying straight onto mm. So don't do that.

...

Quoted 10 lines

Or revert

ftp://ftp.kernel.org/pub/linux/kernel/people/akpm/patches/2.6/2.6.9-rc1/2.6.9-rc1-mm1/broken-out/re-fix-pagecache-reading-off-by-one-cleanup.patch

and then

ftp://ftp.kernel.org/pub/linux/kernel/people/akpm/patches/2.6/2.6.9-rc1/2.6.9-rc1-mm1/broken-out/re-fix-pagecache-reading-off-by-one.patch

Once you have these backed out mine should apply fine, but it only closes some performance (not correctness) corner cases that the above patches attempted to.

#21 lore raw
Quoted 19 lines · Nick Piggin
Quoted 5 lines · Andrew Morton
Quoted 2 lines · Rafael J. Wysocki

Well, guys, to make it 100% clear: if I apply the Nick's patch to the 2.6.9-rc1-mm1 tree, it will fix the data loss issue. Is that right?

Should do.

It passes test cases that would previously fail here, so consider it lightly tested. Note that the patch is on top of 2.6.9-rc1 though, it becomes slightly deranged when applying straight onto mm. So don't do that.

..

Quoted 5 lines

Or revert

ftp://ftp.kernel.org/pub/linux/kernel/people/akpm/patches/2.6/2.6.9-rc1/2 .6.9-rc1-mm1/broken-out/re-fix-pagecache-reading-off-by-one-cleanup.patch

Quoted 8 lines
Quoted 4 lines

and then

ftp://ftp.kernel.org/pub/linux/kernel/people/akpm/patches/2.6/2.6.9-rc1/2 .6.9-rc1-mm1/broken-out/re-fix-pagecache-reading-off-by-one.patch

Once you have these backed out mine should apply fine, but it only closes some performance (not correctness) corner cases that the above patches attempted to.

OK. Thanks a lot,

RJW

Signature · 3 lines
For a successful technology, reality must take precedence over public 
relations, for nature cannot be fooled.
					-- Richard P. Feynman
#22 lore raw
In reply to Nick Piggin
Quoted 3 lines · Nick Piggin

Ahh, yep - Hugh just forgot to also move the "nr" calculation into the ->readpage path, so it hits twice on the fast path.

Yes, your patch is better than mine.

Quoted 3 lines

What's more, it looks like mine handles the corner case of reading off the end of a non-PAGE_SIZE file (but within the same page). I think yours will drop through and do the ->readpage, while mine doesn't...?

It's a common case, not a corner case: a short read to end of file, then app does another read which returns 0 for the end of file: that wouldn't normally fall through to readpage in Ram's case, but would do unnecessary page_cache_readahead (won't do much) and find_get_page.

Quoted 1 line

I agree. We'll leave it to someone else to decide, then ;)

I vote for Nick's patch.

I do have one reservation on do_generic_mapping_read, common to all these versions, unrelated to the current issue.

I'm surprised to notice that you're careful to re-i_size_read after readpage, but otherwise rely on the initial i_size_read. We could go around this loop many many times, faulting user pages in actor, rescheduling away: the old (e.g. 2.4 or 2.6.0) code was deficient after readpage, but at least it reassessed i_size each time around the loop. I guess if the file is truncated meanwhile, the common case would be for a find_get_page to fail, and then the situation be corrected after readpage; perhaps it's more likely to show up as read returning too little on a large file being steadily appended. Maybe you already ruled these cases out as not worth the overhead of handling, but it does surprise me that the old code was safer in this respect.

Hugh

#23 lore raw
Quoted 14 lines · Hugh Dickins
Quoted 2 lines · Nick Piggin

Ahh, yep - Hugh just forgot to also move the "nr" calculation into the ->readpage path, so it hits twice on the fast path.

Yes, your patch is better than mine.

Quoted 3 lines

What's more, it looks like mine handles the corner case of reading off the end of a non-PAGE_SIZE file (but within the same page). I think yours will drop through and do the ->readpage, while mine doesn't...?

Quoted 5 lines

It's a common case, not a corner case: a short read to end of file, then app does another read which returns 0 for the end of file: that wouldn't normally fall through to readpage in Ram's case, but would do unnecessary page_cache_readahead (won't do much) and find_get_page.

Ahh, yeah I guess that would be the more common case. I was just thinking of just randomly reading past the end of the file - in that case it *would* fall through to ->readpage if the page wasn't in cache.

But anyway, I think we agree my (our) version should cover that.

Quoted 6 lines
Quoted 2 lines

I agree. We'll leave it to someone else to decide, then ;)

I vote for Nick's patch.

OK - maybe that can go for a spin in the next -mm. Andrew did you get it?

Quoted 16 lines

I do have one reservation on do_generic_mapping_read, common to all these versions, unrelated to the current issue.

I'm surprised to notice that you're careful to re-i_size_read after readpage, but otherwise rely on the initial i_size_read. We could go around this loop many many times, faulting user pages in actor, rescheduling away: the old (e.g. 2.4 or 2.6.0) code was deficient after readpage, but at least it reassessed i_size each time around the loop. I guess if the file is truncated meanwhile, the common case would be for a find_get_page to fail, and then the situation be corrected after readpage; perhaps it's more likely to show up as read returning too little on a large file being steadily appended. Maybe you already ruled these cases out as not worth the overhead of handling, but it does surprise me that the old code was safer in this respect.

Yeah I guess it is a case of doing the minimum that is really needed.

Although considering page_cache_readahead call can do an i_size_read, it might be nicer to probably change the interface to have it passed down instead

#24 lore raw
Quoted 2 lines · Nick Piggin
Quoted 1 line · Hugh Dickins

On Sat, 28 Aug 2004, Nick Piggin wrote:

..snip..

Quoted 4 lines

But anyway, I think we agree my (our) version should cover that.

Quoted 1 line
Quoted 8 lines
Quoted 5 lines
Quoted 1 line

I agree. We'll leave it to someone else to decide, then ;)

I vote for Nick's patch.

OK - maybe that can go for a spin in the next -mm. Andrew did you get it?

So in case my vote counts, add my vote too :) .

Quoted 18 lines
Quoted 4 lines

I do have one reservation on do_generic_mapping_read, common to all these versions, unrelated to the current issue.

Quoted 13 lines

I'm surprised to notice that you're careful to re-i_size_read after readpage, but otherwise rely on the initial i_size_read. We could go around this loop many many times, faulting user pages in actor, rescheduling away: the old (e.g. 2.4 or 2.6.0) code was deficient after readpage, but at least it reassessed i_size each time around the loop. I guess if the file is truncated meanwhile, the common case would be for a find_get_page to fail, and then the situation be corrected after readpage; perhaps it's more likely to show up as read returning too little on a large file being steadily appended. Maybe you already ruled these cases out as not worth the overhead of handling, but it does surprise me that the old code was safer in this respect.

Quoted 6 lines

Yeah I guess it is a case of doing the minimum that is really needed.

Although considering page_cache_readahead call can do an i_size_read, it might be nicer to probably change the interface to have it passed down instead

We are experimenting some patches to see if sending the i_size parameter to page_cache_readahead() can help or not. There are couple of advantages in doing that. (1) Quick ramp-up to max-readahead pages for sequential workload. (2) being able to read atleast the requested number of pages in one shot instead of reading one page at a time in case the readahead window gets closed.

But the biggest performance boost has been seen with large max-readahead window sizes. Currently most of the underlying block devices default to 32 pages max-readahead even though the underlying device can handle much larger reads. We could extract much more sequential read performance if the max-readahead was set to much higher values like 256 pages which most modern devices are capable off. The problem AFAICT is that the block device layer defaults the max-readahead value for most block devices to 32, without consulting the capability of the underlying block device driver.

RP

#25 lore raw
Quoted 6 lines · Ram Pai
Quoted 3 lines

OK - maybe that can go for a spin in the next -mm. Andrew did you get it?

So in case my vote counts, add my vote too :) .

Can someone send me the patch?

Quoted 10 lines

But the biggest performance boost has been seen with large max-readahead window sizes. Currently most of the underlying block devices default to 32 pages max-readahead even though the underlying device can handle much larger reads. We could extract much more sequential read performance if the max-readahead was set to much higher values like 256 pages which most modern devices are capable off. The problem AFAICT is that the block device layer defaults the max-readahead value for most block devices to 32, without consulting the capability of the underlying block device driver.

This can be done in startup scripts.