Skip to contentSkip to conversation

reiser4: precise discard - general case

PATCH In review Edward Shishkin 2 participants 9 messages Dec 20, 2014 – Mar 9, 2015

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 reiser4: precise discard - general case

This is the promised generalization, which is supposed to work for all discard offsets and all discard unit sizes without any restrictions.

Complications in comparison with the previous implementation:

In this general case we need "precise" coordinates, where every individual byte can be addressed. All local variables, which represent precise coordinates are denoted with "prefixes" (a_len, d_off, p_tailp, etc). Local variables, which represent "non-precise" coordinates (they are usually of type reiser4_block_nr) are denoted without prefixes (start, len, end, tailp, etc).

Blocks, which contain head and tail paddings are now calculated using the function size_in_blocks(), which actually is an expression for the minimal number of blocks containing the precise extent.

The next trouble is "peculiarity in 0", encountered when calculating the blocks of head padding. if discard offset is different from 0, then the first discard unit of the partition is partial (its other part doesn't belong to our partition, so we can not discard it). We handle this peculiarity by an additional check.

In other bits everything is the same.

Possible optimization: If discard unit sizes are always powers of 2, then it makes sense to replace "do_div(offset, unit_size)" with "offset & (unit_size - 1)".

Mount options discard.offset=xxx,discard.unit=yyy are to emulate various discard unit sizes and offsets on devices _without_ trim support (e.g. HDDs). This is only for debugging purposes, don't use it for real SSD devices: the kernel retrieves the discard parameters on its own.

This patch is against the patch series of Ivan Shapovalov:
http://marc.info/?l=reiserfs-devel&m=141841865432082&w=2

Current status: not well-tested.

Edward.

---
 fs/reiser4/discard.c |  213 +++++++++++++++++++++++++--------------------------
 1 file changed, 106 insertions(+), 107 deletions(-)
fs/reiser4/discard.c +106 -107
@@ -68,39 +68,13 @@
 #include "debug.h"
 #include "txnmgr.h"
 #include "super.h"
+#include "plugin/cluster.h"
 
 #include <linux/slab.h>
 #include <linux/fs.h>
 #include <linux/blkdev.h>
 #include <linux/lcm.h>
 
-/*
- * For 1-dimension integer lattice (a,b) (a - offset, b - step)
- * find its minimal sub-lattice which can be represented in the
- * more coarse grained (scaled with factor r >= 1) coordinates.
- * If representation is impossible, return 1. Otherwise return 0.
- *
- * @a - offset of the lattice in the initial coordinates;
- * @b - step of the lattice in the initial coordinates;
- * @x - offset of the sub-lattice in the scaled coordinates;
- * @y - step of the sub-lattice in the scaled coordinates;
- * @r - scale factor.
- */
-static int convert_lattice_params(int *x, int *y, int a, int b, int r)
-{
-	assert("edward-1635", b != 0);
-	assert("edward-1636", r >= 1);
-
-	if (a % r)
-		return 1;
-	*x = a / r;
-	*y = lcm(b, r) / r;
-
-	/* normalize offset */
-	*x = *x % *y;
-	return 0;
-}
-
 #define MAX_DISCARD_UNIT_BYTES (1 << 20)
 
 /*
@@ -114,7 +88,6 @@ static int convert_lattice_params(int *x
  */
 void check_discard_params(struct super_block *sb)
 {
-	int ret;
 	reiser4_super_info_data *sbinfo;
 	discard_params *sb_discard;
 	struct queue_limits *limits;
@@ -148,21 +121,11 @@ void check_discard_params(struct super_b
 		warning("", "%s: unsupported erase unit (%d)", sb->s_id, unit);
 		goto disable;
 	}
-	ret = convert_lattice_params(&sb_discard->offset,
-				     &sb_discard->unit,
-				     offset,
-				     unit,
-				     sb->s_blocksize);
-	if (ret) {
-		warning("", "%s: unsupported alignment (%d)", sb->s_id, offset);
-		goto disable;
-	}
-	if (sb_discard->unit > MAX_DISCARD_UNIT_BYTES / sb->s_blocksize) {
-		warning("", "%s: unsupported erase unit (%d)", sb->s_id, unit);
-		goto disable;
-	}
+	sb_discard->offset = offset;
+	sb_discard->unit = unit;
+
 	printk("reiser4: %s: enable discard support "
-	       "(erase unit %u blocks, alignment %u blocks)\n",
+	       "(erase unit %u bytes, alignment %u bytes)\n",
 	       sb->s_id, sb_discard->unit, sb_discard->offset);
 	return;
 disable:
@@ -171,8 +134,8 @@ disable:
 	return;
 }
 
-static int __discard_extent(struct block_device *bdev, sector_t start,
-                            sector_t len)
+static int discard_precise_extent(struct block_device *bdev, sector_t start,
+				  sector_t len)
 {
 	assert("intelfx-21", bdev != NULL);
 
@@ -181,27 +144,31 @@ static int __discard_extent(struct block
 }
 
 /*
- * Return size of head padding of an extent on a lattice
- * with step @ulen and offset @uoff.
- * @start - the start offset of the extent.
+ * @start - start of precise extent (offset of the first byte);
+ * return lenght of the head padding in bytes
  */
-static int extent_get_headp(reiser4_block_nr start, int uoff, int ulen)
+static int precise_extent_headp(__u64 start, int uoff, int ulen)
 {
 	__u64 headp;
-	headp = ulen + start - uoff;
+
+	assert("edward-1636", uoff < ulen);
+
+	headp = start + ulen - uoff;
 	headp = do_div(headp, ulen);
 	return headp;
 }
 
 /*
- * Return size of tail padding of an extent on a lattice
- * with step @ulen and offset @uoff.
- * @end - the end offset of the extent.
+ * @end - end of precise extent (offset of the last byte + 1);
+ * return lenght of the tail padding in bytes
  */
-static int extent_get_tailp(reiser4_block_nr end, int uoff, int ulen)
+static int precise_extent_tailp(__u64 end, int uoff, int ulen)
 {
 	__u64 tailp;
-	tailp = ulen + end - uoff;
+
+	assert("edward-1637", uoff < ulen);
+
+	tailp = end + ulen - uoff;
 	tailp = do_div(tailp, ulen);
 	if (tailp)
 		tailp = ulen - tailp;
@@ -228,8 +195,9 @@ static inline int check_free_blocks(cons
 	 * The discard code is ran after the pre-commit hook so deallocated block
 	 * accounting is already done.
 	 */
-	return reiser4_alloc_blocks_exact (&start, &len, BLOCK_NOT_COUNTED,
-					   BA_FORMATTED) == 0;
+	return reiser4_alloc_blocks_exact(&start,
+					  &len,
+					  BLOCK_NOT_COUNTED, BA_FORMATTED) == 0;
 }
 
 /* Make sure that extents are sorted and merged */
@@ -258,34 +226,33 @@ static inline void check_blocknr_list_at
 #endif
 
 /*
- * discard_sorted_merged_extents() - scan the list of sorted and
- * merged extents and check head and tail paddings of each
- * extent in the working space map. Try to "glue" the nearby
- * extents. Discard the (glued) extents with padded (or cut)
- * head and tail.
- * The paddings, if any, are allocated before discarding, and the list
+ * discard_precise_extents() - scan the list of sorted and merged extents and
+ * check head and tail paddings of each extent in the working space map. Try to
+ * "glue" the nearby extents. Discard the resulted (glued, padded, or cut)
+ * extents.
+ *
+ * Head and tail paddings, if any, are allocated before discarding, and the list
  * is updated to contain all new allocations.
  *
- * Pre-conditions: @head points to the list of sorted and
- * merged extents.
+ * Pre-conditions: @head points to the list of sorted and merged extents.
  *
  * Local variables:
  *
- * d_uni - discard unit size (in blocks);
- * d_off - discard alignment (in blocks);
+ * d_uni - discard unit size (in bytes);
+ * d_off - discard alignment (in bytes);
  *
- * start - offset of the first block of the extent;
- * len - length of the extent;
- * end - offset of the first block beyond extent;
+ * start - start of extent (in blocks);
+ * len - length of extent (in blocks);
+ * end - end of extent (last block + 1);
  *
- * headp - size of head padding of the extent;
- * tailp - size of tail padding of the extent;
+ * headp - size of head padding of extent (in blocks);
+ * tailp - size of tail padding of extent (in blocks);
  *
- * astart - actual start to discard (offset of the head padding);
- * alen - actual length to discard (length of glued aligned and padded extents).
+ * a_start - start of resulted (glued, aligned and padded) extent to discard;
+ * a_len - length of resulted (glued, aligned and padded) extent to discard;
  *
- * estart - start of extent to be written back to the list
- * eend - end (last block + 1) of extent to be written back to the list
+ * estart - start of extent to be written back to the list (in blocks);
+ * eend - end (last block + 1) of extent to be written back to the list;
  *
  * Terminology in the comments:
  *
@@ -293,14 +260,15 @@ static inline void check_blocknr_list_at
  * tail - a part of extent at the end.
  */
 
-static int discard_sorted_merged_extents(struct list_head *head)
+static int discard_precise_extents(struct list_head *head)
 {
 	int ret;
-	struct super_block *sb = reiser4_get_current_sb();
 	int d_uni;
 	int d_off;
 	struct list_head *pos;
+	struct super_block *sb = reiser4_get_current_sb();
 	int headp_is_known_dirty = 0;
+	int blkbits = sb->s_blocksize_bits;
 
 	d_off = get_super_private(sb)->discard.offset;
 	d_uni = get_super_private(sb)->discard.unit;
@@ -308,61 +276,82 @@ static int discard_sorted_merged_extents
 	for (pos = head->next; pos != head; pos = pos->next) {
 		int headp;
 		int tailp;
+
+		int p_headp;
+		int p_tailp;
+
 		reiser4_block_nr start;
 		reiser4_block_nr len;
 		reiser4_block_nr end;
-		reiser4_block_nr astart; __s64 alen;
+
 		reiser4_block_nr estart, eend;
 
+		__u64 a_start;
+		__s64 a_len;
+
+		__u64 p_start;
+		__u64 p_len;
+		__u64 p_end;
+
 		check_blocknr_list_at(pos, head);
 
 		start = blocknr_list_entry_start(pos);
 		len = blocknr_list_entry_len(pos);
 		estart = start;
 
+		p_start = start << blkbits;
+		p_len = len << blkbits;
+
 		/*
 		 * Step I. Cut or pad the head of extent
 		 *
 		 * This extent wasn't glued
 		 */
-		headp = extent_get_headp(start, d_off, d_uni);
+
+		p_headp = precise_extent_headp(p_start, d_off, d_uni);
+		headp = size_in_blocks(p_headp, blkbits);
 
 		if (headp == 0) {
 			/*
 			 * empty head padding
 			 */
 			assert("edward-1635", headp_is_known_dirty == 0);
-			astart = start;
-			alen = len;
-		} else if (!headp_is_known_dirty &&
-			   check_free_blocks(start - headp, headp)) {
+			a_start = p_start;
+			a_len = p_len;
+		}
+		else if (p_start >= p_headp /* discard unit is complete */ &&
+			 !headp_is_known_dirty &&
+			 check_free_blocks(start - headp, headp)) {
 			/*
-			 * head padding is clean,
 			 * pad the head
 			 */
-			astart = start - headp;
-			alen = len + headp;
+			a_start = p_start - p_headp;
+			a_len = p_len + p_headp;
 			estart -= headp;
 		} else {
 			/*
 			 * head padding is dirty,
+			 * or discard unit is incomplete (can not
+			 * check blocks outside of the partition),
 			 * cut the head
 			 */
 			headp_is_known_dirty = 0;
-			astart = start + (d_uni - headp);
-			alen = len - (d_uni - headp);
+			a_start = p_start + (d_uni - p_headp);
+			a_len = p_len - (d_uni - p_headp);
 		}
-
 		/*
 		 * Step II. Try to glue all nearby extents to the tail
 		 *          Cut or pad the tail of the last extent.
 		 */
 		end = start + len;
 		eend = end;
-		tailp = extent_get_tailp(end, d_off, d_uni);
+		p_end = end << blkbits;
 
+		p_tailp = precise_extent_tailp(p_end, d_off, d_uni);
+		tailp = size_in_blocks(p_tailp, blkbits);
 		/*
-		 * This "gluing" loop updates @end, @tailp, @alen, @eend
+		 * This "gluing" loop updates
+		 * @end, @p_end, @tailp, @p_tailp, @a_len, @eend
 		 */
 		while (1) {
 			struct list_head *next;
@@ -370,23 +359,36 @@ static int discard_sorted_merged_extents
 			next = get_next_at(pos, head);
 			check_blocknr_list_at(next, head);
 
-			if (next && (end + tailp >= blocknr_list_entry_start(next))) {
+			if (next && (p_end + p_tailp >= blocknr_list_entry_start(next) << blkbits)) {
 				/*
 				 * try to glue the next extent
 				 */
 				reiser4_block_nr next_start;
 				reiser4_block_nr next_len;
 
+				__u64 p_next_start;
+				__u64 p_next_len;
+
 				next_start = blocknr_list_entry_start(next);
 				next_len = blocknr_list_entry_len(next);
 
+				p_next_start = next_start << blkbits;
+				p_next_len = next_len << blkbits;
+
+				/*
+				 * check space between the extents;
+				 * if it is free, then allocate it
+				 */
 				if (check_free_blocks(end, next_start - end)) {
 					/*
 					 * jump to the glued extent
 					 */
-					alen += (next_start + next_len - end);
+					a_len += (p_next_start + p_next_len - p_end);
 					end = next_start + next_len;
-					tailp = extent_get_tailp(end, d_off, d_uni);
+					p_end = end << blkbits;
+
+					p_tailp = precise_extent_tailp(p_end, d_off, d_uni);
+					tailp = size_in_blocks(p_tailp, blkbits);
 					eend = end;
 					/*
 					 * remove the glued extent from the list
@@ -403,10 +405,10 @@ static int discard_sorted_merged_extents
 					/*
 					 * gluing failed, cut the tail
 					 */
-					if (end + tailp > next_start)
+					if (p_end + p_tailp > p_next_start)
 						headp_is_known_dirty = 1;
 
-					alen -= (d_uni - tailp);
+					a_len -= (d_uni - p_tailp);
 					break;
 				}
 
@@ -425,14 +427,14 @@ static int discard_sorted_merged_extents
 					 * tail padding is clean,
 					 * pad the tail
 					 */
-					alen += tailp;
+					a_len += p_tailp;
 					eend += tailp;
 				} else
 					/*
 					 * tail padding is dirty,
 					 * cut the tail
 					 */
-					alen -= (d_uni - tailp);
+					a_len -= (d_uni - p_tailp);
 				break;
 			}
 		}
@@ -440,12 +442,10 @@ static int discard_sorted_merged_extents
 		/*
 		 * Step III. Discard the result
 		 */
-		if (alen > 0) {
+		if (a_len > 0) {
 			assert("intelfx-74", estart < eend);
 			assert("intelfx-75", estart <= start);
-			assert("intelfx-76", estart <= astart);
 			assert("intelfx-77", start + len <= eend);
-			assert("intelfx-78", astart + alen <= eend);
 
 			/* here @eend becomes length */
 			eend -= estart;
@@ -453,9 +453,8 @@ static int discard_sorted_merged_extents
 			       reiser4_check_blocks(&estart, &eend, 1));
 			blocknr_list_update_extent(pos, &estart, &eend);
 
-			ret = __discard_extent(sb->s_bdev,
-					       astart * (sb->s_blocksize >> 9),
-					       alen * (sb->s_blocksize >> 9));
+			ret = discard_precise_extent(sb->s_bdev,
+						     a_start >> 9, a_len >> 9);
 			if (ret)
 				return ret;
 		}
@@ -475,7 +474,7 @@ int discard_atom(txn_atom *atom, struct
 	}
 
 	assert("intelfx-28", atom != NULL);
-	assert("intelfx-59", processed_entries != NULL);
+	assert("intelfx-59", processed_set != NULL);
 
 	if (list_empty(&atom->discard.delete_set)) {
 		/* Nothing left to discard. */
@@ -492,7 +491,7 @@ int discard_atom(txn_atom *atom, struct
 	blocknr_list_sort_and_join(&discard_set);
 
 	/* Perform actual dirty work. The discard set may change at this point. */
-	ret = discard_sorted_merged_extents(&discard_set);
+	ret = discard_precise_extents(&discard_set);
 
 	/* Add processed extents to the temporary list. */
 	blocknr_list_merge(&discard_set, processed_set);
@@ -508,7 +507,7 @@ int discard_atom(txn_atom *atom, struct
 void discard_atom_post(txn_atom *atom, struct list_head *processed_set)
 {
 	assert("intelfx-60", atom != NULL);
-	assert("intelfx-61", processed_entries != NULL);
+	assert("intelfx-61", processed_set != NULL);
 
 	if (!reiser4_is_set(reiser4_get_current_sb(), REISER4_DISCARD)) {
 		spin_unlock_atom(atom);
#1 lore raw
Quoted 43 lines · Edward Shishkin

This is the promised generalization, which is supposed to work for all discard
offsets and all discard unit sizes without any restrictions.

Complications in comparison with the previous implementation:

In this general case we need "precise" coordinates, where every individual byte
can be addressed. All local variables, which represent precise coordinates are
denoted with "prefixes" (a_len, d_off, p_tailp, etc). Local variables, which represent
"non-precise" coordinates (they are usually of type reiser4_block_nr) are denoted
without prefixes (start, len, end, tailp, etc).

Blocks, which contain head and tail paddings are now calculated using the function size_in_blocks(), which actually is an expression for the minimal number
of blocks containing the precise extent.

The next trouble is "peculiarity in 0", encountered when calculating the blocks of
head padding. if discard offset is different from 0, then the first discard unit of the
partition is partial (its other part doesn't belong to our partition, so we can not
discard it). We handle this peculiarity by an additional check.

In other bits everything is the same.

Possible optimization: If discard unit sizes are always powers of 2, then it makes
sense to replace "do_div(offset, unit_size)" with "offset & (unit_size - 1)".

Mount options discard.offset=xxx,discard.unit=yyy are to emulate various discard unit sizes and offsets on devices _without_ trim support (e.g. HDDs).
This is only for debugging purposes, don't use it for real SSD devices:
the kernel
retrieves the discard parameters on its own.

Quoted 6 lines

This patch is against the patch series of Ivan Shapovalov:
http://marc.info/?l=reiserfs-devel&m=141841865432082&w=2

Current status: not well-tested.

Edward.

Hi,

I've found a bug in our implementation (don't know when it appeared, maybe it was quite some time ago). I've intended to fix it and send a patch along with description, but I still can't think of a viable fix.

So: the problem is that check_free_blocks() isn't idempotent, because it allocates blocks if the whole extent is clean. Therefore, it must not be called for overlapping ranges. However, in some conditions tail padding of some extent and head padding of next extent may overlap in terms of disk blocks (gluing code only catches overlapping erase units).

This will yield a false negative when checking the head padding, so it does not lead to any data losses (just to inefficiency).

Comments appreciated...

Thanks,

Signature · 1 line
Ivan Shapovalov / intelfx /
#2 lore raw
Quoted 56 lines · Ivan Shapovalov
Quoted 43 lines · Edward Shishkin

This is the promised generalization, which is supposed to work for all discard
offsets and all discard unit sizes without any restrictions.

Complications in comparison with the previous implementation:

In this general case we need "precise" coordinates, where every individual byte
can be addressed. All local variables, which represent precise coordinates are
denoted with "prefixes" (a_len, d_off, p_tailp, etc). Local variables, which represent
"non-precise" coordinates (they are usually of type reiser4_block_nr) are denoted
without prefixes (start, len, end, tailp, etc).

Blocks, which contain head and tail paddings are now calculated using the function size_in_blocks(), which actually is an expression for the minimal number
of blocks containing the precise extent.

The next trouble is "peculiarity in 0", encountered when calculating the blocks of
head padding. if discard offset is different from 0, then the first discard unit of the
partition is partial (its other part doesn't belong to our partition, so we can not
discard it). We handle this peculiarity by an additional check.

In other bits everything is the same.

Possible optimization: If discard unit sizes are always powers of 2, then it makes
sense to replace "do_div(offset, unit_size)" with "offset & (unit_size - 1)".

Mount options discard.offset=xxx,discard.unit=yyy are to emulate various discard unit sizes and offsets on devices _without_ trim support (e.g. HDDs).
This is only for debugging purposes, don't use it for real SSD devices:
the kernel
retrieves the discard parameters on its own.

Quoted 6 lines

This patch is against the patch series of Ivan Shapovalov:
http://marc.info/?l=reiserfs-devel&m=141841865432082&w=2

Current status: not well-tested.

Edward.

Hi,

I've found a bug in our implementation (don't know when it appeared, maybe it was quite some time ago). I've intended to fix it and send a patch along with description, but I still can't think of a viable fix.

Quoted 8 lines

So: the problem is that check_free_blocks() isn't idempotent, because it allocates blocks if the whole extent is clean. Therefore, it must not be called for overlapping ranges. However, in some conditions tail padding of some extent and head padding of next extent may overlap in terms of disk blocks (gluing code only catches overlapping erase units).

This will yield a false negative when checking the head padding, so it does not lead to any data losses (just to inefficiency).

You mean that sometimes we perform unneeded checks? I see nothing criminal, as we don't exceed announced (2N_e) number of checks, where N_e is number of extents in the discard set.

As to fixup: I think that we need to set up the local variable head_is_known_dirty properly..

Thanks,
Edward.

#3 lore raw
Quoted 63 lines · Edward Shishkin
Quoted 62 lines · Ivan Shapovalov
Quoted 43 lines · Edward Shishkin

This is the promised generalization, which is supposed to work for all discard
offsets and all discard unit sizes without any restrictions.

Complications in comparison with the previous implementation:

In this general case we need "precise" coordinates, where every individual byte
can be addressed. All local variables, which represent precise coordinates are
denoted with "prefixes" (a_len, d_off, p_tailp, etc). Local variables, which represent
"non-precise" coordinates (they are usually of type reiser4_block_nr) are denoted
without prefixes (start, len, end, tailp, etc).

Blocks, which contain head and tail paddings are now calculated using the function size_in_blocks(), which actually is an expression for the minimal number
of blocks containing the precise extent.

The next trouble is "peculiarity in 0", encountered when calculating the blocks of
head padding. if discard offset is different from 0, then the first discard unit of the
partition is partial (its other part doesn't belong to our partition, so we can not
discard it). We handle this peculiarity by an additional check.

In other bits everything is the same.

Possible optimization: If discard unit sizes are always powers of 2, then it makes
sense to replace "do_div(offset, unit_size)" with "offset & (unit_size - 1)".

Mount options discard.offset=xxx,discard.unit=yyy are to emulate various discard unit sizes and offsets on devices _without_ trim support (e.g. HDDs).
This is only for debugging purposes, don't use it for real SSD devices:
the kernel
retrieves the discard parameters on its own.

Quoted 6 lines

This patch is against the patch series of Ivan Shapovalov:
http://marc.info/?l=reiserfs-devel&m=141841865432082&w=2

Current status: not well-tested.

Edward.

Hi,

I've found a bug in our implementation (don't know when it appeared, maybe it was quite some time ago). I've intended to fix it and send a patch along with description, but I still can't think of a viable fix.

So: the problem is that check_free_blocks() isn't idempotent, because it allocates blocks if the whole extent is clean. Therefore, it must not be called for overlapping ranges. However, in some conditions tail padding of some extent and head padding of next extent may overlap in terms of disk blocks (gluing code only catches overlapping erase units).

Quoted 8 lines
Quoted 2 lines

This will yield a false negative when checking the head padding, so it does not lead to any data losses (just to inefficiency).

You mean that sometimes we perform unneeded checks? I see nothing criminal, as we don't exceed announced (2N_e) number of checks, where N_e is number of extents in the discard set.

No, I didn't talk about that.

Quoted 3 lines

As to fixup: I think that we need to set up the local variable head_is_known_dirty properly..

Hmm, head_is_known_dirty is an optimization: either known dirty (in which case we skip checking and cut the head), or unknown (in which case we do the check).

I'm talking about a different scenario:
- tail padding of an extent is clean
- head padding of the next extent is clean
- these two paddings overlap in terms of disk blocks

In this case, the head padding check will yield false ("dirty") because part of it has been already allocated for the tail padding, but in fact it is clean. Thus a false negative: the head will be cut while it can be padded.

More generally, our check_free_blocks() is not idempotent. After a positive result has been returned for a given range [A;B), check_free_blocks() must not be called for any range [A';B') such that [A;B) ∩ [A';B') ≠ ∅. Otherwise a false negative can be returned.

Actually, during the last night I've *apparently* came up with a solution for this :) I'll send a patch once I figure out how to explain it properly in the comments...

Thanks,

Signature · 1 line
Ivan Shapovalov / intelfx /
#4 lore raw
Quoted 84 lines · Ivan Shapovalov
Quoted 63 lines · Edward Shishkin
Quoted 62 lines · Ivan Shapovalov
Quoted 43 lines · Edward Shishkin

This is the promised generalization, which is supposed to work for all discard
offsets and all discard unit sizes without any restrictions.

Complications in comparison with the previous implementation:

In this general case we need "precise" coordinates, where every individual byte
can be addressed. All local variables, which represent precise coordinates are
denoted with "prefixes" (a_len, d_off, p_tailp, etc). Local variables, which represent
"non-precise" coordinates (they are usually of type reiser4_block_nr) are denoted
without prefixes (start, len, end, tailp, etc).

Blocks, which contain head and tail paddings are now calculated using the function size_in_blocks(), which actually is an expression for the minimal number
of blocks containing the precise extent.

The next trouble is "peculiarity in 0", encountered when calculating the blocks of
head padding. if discard offset is different from 0, then the first discard unit of the
partition is partial (its other part doesn't belong to our partition, so we can not
discard it). We handle this peculiarity by an additional check.

In other bits everything is the same.

Possible optimization: If discard unit sizes are always powers of 2, then it makes
sense to replace "do_div(offset, unit_size)" with "offset & (unit_size - 1)".

Mount options discard.offset=xxx,discard.unit=yyy are to emulate various discard unit sizes and offsets on devices _without_ trim support (e.g. HDDs).
This is only for debugging purposes, don't use it for real SSD devices:
the kernel
retrieves the discard parameters on its own.

Quoted 6 lines

This patch is against the patch series of Ivan Shapovalov:
http://marc.info/?l=reiserfs-devel&m=141841865432082&w=2

Current status: not well-tested.

Edward.

Hi,

I've found a bug in our implementation (don't know when it appeared, maybe it was quite some time ago). I've intended to fix it and send a patch along with description, but I still can't think of a viable fix.

So: the problem is that check_free_blocks() isn't idempotent, because it allocates blocks if the whole extent is clean. Therefore, it must not be called for overlapping ranges. However, in some conditions tail padding of some extent and head padding of next extent may overlap in terms of disk blocks (gluing code only catches overlapping erase units).

Quoted 7 lines
Quoted 2 lines

This will yield a false negative when checking the head padding, so it does not lead to any data losses (just to inefficiency).

You mean that sometimes we perform unneeded checks? I see nothing criminal, as we don't exceed announced (2N_e) number of checks, where N_e is number of extents in the discard set.

No, I didn't talk about that.

Quoted 2 lines

As to fixup: I think that we need to set up the local variable head_is_known_dirty properly..

Hmm, head_is_known_dirty is an optimization: either known dirty (in which case we skip checking and cut the head), or unknown (in which case we do the check).

I'm talking about a different scenario:
- tail padding of an extent is clean
- head padding of the next extent is clean
- these two paddings overlap in terms of disk blocks

Quoted 4 lines

In this case, the head padding check will yield false ("dirty") because part of it has been already allocated for the tail padding, but in fact it is clean. Thus a false negative: the head will be cut while it can be padded.

Ah, you suspect non-preciseness (leak of "garbage")?

If tail padding of the current extent overlaps with the head padding of the next extent, then the end of the current extent and the beginning of the next extent are in the same erase unit. Otherwise we'll end with contradiction. Correct? Now note that we'll try to glue such extents (the current one and its right neighbor).

If this gluing failed (the "area" between the extents is dirty), then we set head_is_known_dirty = 1, so that head padding of the next extent won't be checked.

If this gluing successful, then we won't check the head of the next extent. Because we jump to this next extent and try to glue its right neighbor.

That is, I still don't see any problems.

Edward.

Quoted 10 lines

More generally, our check_free_blocks() is not idempotent. After a positive result has been returned for a given range [A;B), check_free_blocks() must not be called for any range [A';B') such that [A;B) ∩ [A';B') ≠ ∅. Otherwise a false negative can be returned.

Actually, during the last night I've *apparently* came up with a solution for this :) I'll send a patch once I figure out how to explain it properly in the comments...

Thanks,

--
To unsubscribe from this list: send the line "unsubscribe reiserfs-devel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#5 lore raw
Quoted 94 lines

12 февр. 2015 г., в 2:40, Edward Shishkin <[email protected]> написал(а):

Quoted 85 lines · Ivan Shapovalov
Quoted 64 lines · Edward Shishkin
Quoted 63 lines · Ivan Shapovalov
Quoted 44 lines · Edward Shishkin

This is the promised generalization, which is supposed to work for all discard
offsets and all discard unit sizes without any restrictions.

Complications in comparison with the previous implementation:

In this general case we need "precise" coordinates, where every individual byte
can be addressed. All local variables, which represent precise coordinates are
denoted with "prefixes" (a_len, d_off, p_tailp, etc). Local variables, which represent
"non-precise" coordinates (they are usually of type reiser4_block_nr) are denoted
without prefixes (start, len, end, tailp, etc).

Blocks, which contain head and tail paddings are now calculated using the function size_in_blocks(), which actually is an expression for the minimal number
of blocks containing the precise extent.

The next trouble is "peculiarity in 0", encountered when calculating the blocks of
head padding. if discard offset is different from 0, then the first discard unit of the
partition is partial (its other part doesn't belong to our partition, so we can not
discard it). We handle this peculiarity by an additional check.

In other bits everything is the same.

Possible optimization: If discard unit sizes are always powers of 2, then it makes
sense to replace "do_div(offset, unit_size)" with "offset & (unit_size - 1)".

Mount options discard.offset=xxx,discard.unit=yyy are to emulate various discard unit sizes and offsets on devices _without_ trim support (e.g. HDDs).
This is only for debugging purposes, don't use it for real SSD devices:
the kernel
retrieves the discard parameters on its own.

Quoted 6 lines

This patch is against the patch series of Ivan Shapovalov:
http://marc.info/?l=reiserfs-devel&m=141841865432082&w=2

Current status: not well-tested.

Edward.

Hi,

I've found a bug in our implementation (don't know when it appeared, maybe it was quite some time ago). I've intended to fix it and send a patch along with description, but I still can't think of a viable fix.

So: the problem is that check_free_blocks() isn't idempotent, because it allocates blocks if the whole extent is clean. Therefore, it must not be called for overlapping ranges. However, in some conditions tail padding of some extent and head padding of next extent may overlap in terms of disk blocks (gluing code only catches overlapping erase units).

Quoted 7 lines
Quoted 2 lines

This will yield a false negative when checking the head padding, so it does not lead to any data losses (just to inefficiency).

You mean that sometimes we perform unneeded checks? I see nothing criminal, as we don't exceed announced (2N_e) number of checks, where N_e is number of extents in the discard set.

No, I didn't talk about that.

Quoted 2 lines

As to fixup: I think that we need to set up the local variable head_is_known_dirty properly..

Hmm, head_is_known_dirty is an optimization: either known dirty (in which case we skip checking and cut the head), or unknown (in which case we do the check).

I'm talking about a different scenario:
- tail padding of an extent is clean
- head padding of the next extent is clean
- these two paddings overlap in terms of disk blocks

Quoted 4 lines

In this case, the head padding check will yield false ("dirty") because part of it has been already allocated for the tail padding, but in fact it is clean. Thus a false negative: the head will be cut while it can be padded.

Quoted 6 lines

Ah, you suspect non-preciseness (leak of "garbage")?

If tail padding of the current extent overlaps with the head padding of the next extent, then the end of the current extent and the beginning of the next extent are in the same erase unit. Otherwise we'll end with contradiction. Correct?

Correct, but I'm talking about a different situation where the end of the current extent and the beginning of the next extent are in distinct erase units but in the same disk block. Then we'll end up checking that disk block twice.

Suppose it is "clean", i. e. we ideally should pad both extents. Now note that our check_free_blocks() is not actually "check" but rather "try-allocate". So, first time we check it for the tail padding, the check succeeds, we pad the tail _and allocate that disk block_. The second time we check it (for the next extent's head padding) we get a false-negative.

--
Ivan Shapovalov / intelfx /

(Sent from a phone. Havoc may be wreaked on the formatting.)-- To unsubscribe from this list: send the line "unsubscribe reiserfs-devel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#6 lore raw
Quoted 22 lines · Ivan Shapovalov
Quoted 22 lines

You mean that sometimes we perform unneeded checks? I see nothing criminal, as we don't exceed announced (2N_e) number of checks, where N_e is number of extents in the discard set.

Quoted 17 lines

No, I didn't talk about that.

Quoted 2 lines

As to fixup: I think that we need to set up the local variable head_is_known_dirty properly..

Hmm, head_is_known_dirty is an optimization: either known dirty (in which case we skip checking and cut the head), or unknown (in which case we do the check).

I'm talking about a different scenario:
- tail padding of an extent is clean
- head padding of the next extent is clean
- these two paddings overlap in terms of disk blocks

In this case, the head padding check will yield false ("dirty") because part of it has been already allocated for the tail padding, but in fact it is clean. Thus a false negative: the head will be cut while it can be padded.

Quoted 7 lines
Quoted 6 lines

Ah, you suspect non-preciseness (leak of "garbage")?

If tail padding of the current extent overlaps with the head padding of the next extent, then the end of the current extent and the beginning of the next extent are in the same erase unit. Otherwise we'll end with contradiction. Correct?

Correct, but I'm talking about a different situation where the end of the current extent and the beginning of the next extent are in distinct erase units but in the same disk block. Then we'll end up checking that disk block twice.

You correctly pointed out the possibility of garbage leak in the case of block_size > erase_unit_size. However, the current gluing policy prevents such leak.

Indeed, let AB be a current extent, and CD - next extent

----*-----*-----*-----*-----*-----*-----*---> units

--|-----------|-----------|-----------|-----> blocks
  A           B           C           D

Note that since extents are sorted and merged, distance between any 2 extents are not smaller than block_size.
In particular, |BC| >= block_size. (1)

Suppose that tail padding of the current extent and head padding of the next extent are in the same disk block. However, in this case, in accordance with the definitions
of tail and head paddings, we have that |BC| <= block_size. (2)

From (1) and (2) we have that |BC| == block_size, and p_end + p_tailp == C == start-of-next-extent. Note, that this satisfies the "gluing condition" (discard.c, line 362). That is, we'll try to glue the current and the next extent. It means that we'll check BC only once:

If BC is dirty, than gluing failed, and we don't allocate BC.

If BC is clean, than we allocate BC, jump to the next extent and don't check its head padding (because it was "glued" with the previous one).

Did I miss something?

Thanks,
Edward.

#7 lore raw
Quoted 18 lines · Edward Shishkin

[...]

You correctly pointed out the possibility of garbage leak in the case of block_size > erase_unit_size. However, the current gluing policy prevents such leak.

Indeed, let AB be a current extent, and CD - next extent

----*-----*-----*-----*-----*-----*-----*---> units --|-----------|-----------|-----------|-----> blocks
A B C D

Note that since extents are sorted and merged, distance between any 2 extents are not smaller than block_size.
In particular, |BC| >= block_size. (1)

Quoted 7 lines

Suppose that tail padding of the current extent and head padding of the next extent are in the same disk block. However, in this case, in accordance with the definitions
of tail and head paddings, we have that |BC| <= block_size. (2)

From (1) and (2) we have that |BC| == block_size, and
p_end + p_tailp == C == start-of-next-extent.

This is not correct. p_* variables are byte-granular, so p_end + p_tailp ends up somewhere between B and C. More precisely, it will point at the first unit-unit boundary inside (B; C). So the condition referenced below is not fulfilled.

That is, we only glue extents which overlap in terms of erase units, not in terms of disk blocks after padding.

One may suggest to relax the condition instead; in other words, to make it look something like this:

if (end + tailp >= blocknr_list_entry_start(next))

However, it potentially makes us discard already clean units. I don't remember if there are any other problems with this approach.

Makes sense?

Thanks,

Signature · 1 line
Ivan Shapovalov / intelfx /
#8 lore raw
Quoted 26 lines · Ivan Shapovalov
Quoted 18 lines · Edward Shishkin

[...]

You correctly pointed out the possibility of garbage leak in the case of block_size > erase_unit_size. However, the current gluing policy prevents such leak.

Indeed, let AB be a current extent, and CD - next extent

----*-----*-----*-----*-----*-----*-----*---> units --|-----------|-----------|-----------|-----> blocks
A B C D

Note that since extents are sorted and merged, distance between any 2 extents are not smaller than block_size.
In particular, |BC| >= block_size. (1)

Quoted 7 lines

Suppose that tail padding of the current extent and head padding of the next extent are in the same disk block. However, in this case, in accordance with the definitions
of tail and head paddings, we have that |BC| <= block_size. (2)

From (1) and (2) we have that |BC| == block_size, and
p_end + p_tailp == C == start-of-next-extent.

Quoted 7 lines

This is not correct. p_* variables are byte-granular, so p_end + p_tailp ends up somewhere between B and C. More precisely, it will point at the first unit-unit boundary inside (B; C). So the condition referenced below is not fulfilled.

That is, we only glue extents which overlap in terms of erase units, not in terms of disk blocks after padding.

Yes, you are right, I was confused with the notations.

Quoted 8 lines

One may suggest to relax the condition instead; in other words, to make it look something like this:

if (end + tailp >= blocknr_list_entry_start(next))

However, it potentially makes us discard already clean units. I don't remember if there are any other problems with this approach.

I guess that excessive discards is also not fine...

Unfortunately the precise solutions looks too complicated:
I simply don't have a time to approve or disapprove it. It seems that next 4 months I'll be busy.

Thanks,
Edward.