Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dmu_zfetch: don't leak unreferenced stream when zfetch is freed #11052

Merged
merged 1 commit into from
Oct 14, 2020

Conversation

mattmacy
Copy link
Contributor

Currently streams are only freed when:

  • They have no referencing zfetch and and their I/O references
    go to zero.
  • They are more than 2s old and a new I/O request comes in on
    the same zfetch.

This means that we will leak unreferenced streams when their zfetch
structure is freed.

This change checks the reference count on a stream at zfetch free
time. If it is zero we free it immediately. If it has remaining
references we allow the prefetch callback to free it at I/O
completion time.

Signed-off-by: Matt Macy [email protected]

Motivation and Context

Description

How Has This Been Tested?

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Performance enhancement (non-breaking change which improves efficiency)
  • Code cleanup (non-breaking change which makes code smaller or more readable)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation (a change to man pages or other documentation)

Checklist:

Currently streams are only freed when:
  - They have no referencing zfetch and and their I/O references
    go to zero.
  - They are more than 2s old and a new I/O request comes in on
    the same zfetch.

This means that we will leak unreferenced streams when their zfetch
structure is freed.

This change checks the reference count on a stream at zfetch free
time. If it is zero we free it immediately. If it has remaining
references we allow the prefetch callback to free it at I/O
completion time.

Signed-off-by: Matt Macy <[email protected]>
@mattmacy mattmacy mentioned this pull request Oct 12, 2020
12 tasks
@behlendorf behlendorf added the Status: Code Review Needed Ready for review and testing label Oct 13, 2020
@adamdmoss
Copy link
Contributor

Why should it ever orphan? Orphan == leak as far as I can tell. :(

@mattmacy
Copy link
Contributor Author

Why should it ever orphan? Orphan == leak as far as I can tell. :(

No, not at all. When the file is closed but the I/O hasn't completed there will still be references.

@adamdmoss
Copy link
Contributor

Why should it ever orphan? Orphan == leak as far as I can tell. :(

No, not at all. When the file is closed but the I/O hasn't completed there will still be references.

Cool, but how is an orphaned stream not leaked? As far as I can tell the only surviving pointer to the stream is in the zf->zf_stream list, which is about to be destroyed.

@mattmacy
Copy link
Contributor Author

Why should it ever orphan? Orphan == leak as far as I can tell. :(

No, not at all. When the file is closed but the I/O hasn't completed there will still be references.

Cool, but how is an orphaned stream not leaked? As far as I can tell the only surviving pointer to the stream is in the zf->zf_stream list, which is about to be destroyed.

The completion routine frees it in that case. Hence the fetch pointer.

@adamdmoss
Copy link
Contributor

Why should it ever orphan? Orphan == leak as far as I can tell. :(

Why should it ever orphan? Orphan == leak as far as I can tell. :(

No, not at all. When the file is closed but the I/O hasn't completed there will still be references.

Cool, but how is an orphaned stream not leaked? As far as I can tell the only surviving pointer to the stream is in the zf->zf_stream list, which is about to be destroyed.

The completion routine frees it in that case. Hence the fetch pointer.

That's how I think it should work, but I don't believe it does. Nowhere does the code access zs_fetch.

I may be wrong - you're the expert - I'd be grateful if you could point me to the code in question so I can smack myself upside the head. :)

@mattmacy
Copy link
Contributor Author

Why should it ever orphan? Orphan == leak as far as I can tell. :(

Why should it ever orphan? Orphan == leak as far as I can tell. :(

No, not at all. When the file is closed but the I/O hasn't completed there will still be references.

Cool, but how is an orphaned stream not leaked? As far as I can tell the only surviving pointer to the stream is in the zf->zf_stream list, which is about to be destroyed.

The completion routine frees it in that case. Hence the fetch pointer.

That's how I think it should work, but I don't believe it does. Nowhere does the code access zs_fetch.

I may be wrong - you're the expert - I'd be grateful if you could point me to the code in question so I can smack myself upside the head. :)

Uhm ... orphan sets it to NULL. The completion routine checks against NULL and then frees it. Not sure how you mean it doesn't access zs_fetch.

static void
dmu_zfetch_stream_orphan(zfetch_t *zf, zstream_t *zs)
{
	ASSERT(MUTEX_HELD(&zf->zf_lock));
	list_remove(&zf->zf_stream, zs);
	zs->zs_fetch = NULL;
	zf->zf_numstreams--;
}

static void
dmu_zfetch_stream_done(void *arg, boolean_t io_issued)
{
	zstream_t *zs = arg;

	if (zs->zs_start_time && io_issued) {
		hrtime_t now = gethrtime();
		hrtime_t delta = NSEC2USEC(now - zs->zs_start_time);

		zs->zs_start_time = 0;
		ZFETCHSTAT_SET(zfetchstat_last_completion_us, delta);
		if (delta > ZFETCHSTAT_GET(zfetchstat_max_completion_us))
			ZFETCHSTAT_SET(zfetchstat_max_completion_us, delta);
	}

	if (zfs_refcount_remove(&zs->zs_blocks, NULL) != 0)
		return;

	/*
	 * The parent fetch structure has gone away
	 */
	if (zs->zs_fetch == NULL)
		dmu_zfetch_stream_fini(zs);
}

@adamdmoss
Copy link
Contributor

I think maybe I see it - dmu_zfetch_stream_done() detects orphans with zs->zs_fetch == NULL and destroys them properly?
Is that right?

@mattmacy
Copy link
Contributor Author

I think maybe I see it - dmu_zfetch_stream_done() detects orphans with zs->zs_fetch == NULL and destroys them properly?
Is that right?

Correct.

@adamdmoss
Copy link
Contributor

Thanks so much, thanks for your patience.
So the leak occurs when the completion routine is called before dmu_zfetch_fini(), but not the other way around, right?

@mattmacy
Copy link
Contributor Author

Thanks so much, thanks for your patience.
So the leak occurs when the completion routine is called before dmu_zfetch_fini(), but not the other way around, right?

Correct.

@adamdmoss
Copy link
Contributor

In that case I wholeheartedly agree with your fix, thanks for your patience. :)

Copy link
Contributor

@adamdmoss adamdmoss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like hubris for me to review this change after I submitted a bogus fix but I approve. 👍

@mattmacy
Copy link
Contributor Author

Seems like hubris for me to review this change after I submitted a bogus fix but I approve. +1

Well, you did find the bug.

@behlendorf behlendorf added Status: Accepted Ready to integrate (reviewed, tested) and removed Status: Code Review Needed Ready for review and testing labels Oct 14, 2020
@behlendorf behlendorf merged commit 57dc5d4 into openzfs:master Oct 14, 2020
ghost pushed a commit to zfsonfreebsd/ZoF that referenced this pull request Oct 17, 2020
Currently streams are only freed when:
  - They have no referencing zfetch and and their I/O references
    go to zero.
  - They are more than 2s old and a new I/O request comes in on
    the same zfetch.

This means that we will leak unreferenced streams when their zfetch
structure is freed.

This change checks the reference count on a stream at zfetch free
time. If it is zero we free it immediately. If it has remaining
references we allow the prefetch callback to free it at I/O
completion time.

Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Adam Moss <[email protected]>
Reviewed-by: Ryan Moeller <[email protected]>
Signed-off-by: Matt Macy <[email protected]>
Closes openzfs#11052
@adamdmoss
Copy link
Contributor

Has this been considered for zfs-2.0.0-rcX? @behlendorf
Cheers.

@behlendorf
Copy link
Contributor

@adamdmoss the original zfetch change was not applied to the 2.0.0 branch so there's no need to pick up this fix.

@adamdmoss
Copy link
Contributor

Oh - hooray!

ghost pushed a commit to truenas/zfs that referenced this pull request Nov 3, 2020
Currently streams are only freed when:
  - They have no referencing zfetch and and their I/O references
    go to zero.
  - They are more than 2s old and a new I/O request comes in on
    the same zfetch.

This means that we will leak unreferenced streams when their zfetch
structure is freed.

This change checks the reference count on a stream at zfetch free
time. If it is zero we free it immediately. If it has remaining
references we allow the prefetch callback to free it at I/O
completion time.

Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Adam Moss <[email protected]>
Reviewed-by: Ryan Moeller <[email protected]>
Signed-off-by: Matt Macy <[email protected]>
Closes openzfs#11052
ghost pushed a commit to zfsonfreebsd/ZoF that referenced this pull request Nov 11, 2020
Currently streams are only freed when:
  - They have no referencing zfetch and and their I/O references
    go to zero.
  - They are more than 2s old and a new I/O request comes in on
    the same zfetch.

This means that we will leak unreferenced streams when their zfetch
structure is freed.

This change checks the reference count on a stream at zfetch free
time. If it is zero we free it immediately. If it has remaining
references we allow the prefetch callback to free it at I/O
completion time.

Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Adam Moss <[email protected]>
Reviewed-by: Ryan Moeller <[email protected]>
Signed-off-by: Matt Macy <[email protected]>
Closes openzfs#11052
ghost pushed a commit to zfsonfreebsd/ZoF that referenced this pull request Nov 12, 2020
Currently streams are only freed when:
  - They have no referencing zfetch and and their I/O references
    go to zero.
  - They are more than 2s old and a new I/O request comes in on
    the same zfetch.

This means that we will leak unreferenced streams when their zfetch
structure is freed.

This change checks the reference count on a stream at zfetch free
time. If it is zero we free it immediately. If it has remaining
references we allow the prefetch callback to free it at I/O
completion time.

Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Adam Moss <[email protected]>
Reviewed-by: Ryan Moeller <[email protected]>
Signed-off-by: Matt Macy <[email protected]>
Closes openzfs#11052
ghost pushed a commit to zfsonfreebsd/ZoF that referenced this pull request Nov 12, 2020
Currently streams are only freed when:
  - They have no referencing zfetch and and their I/O references
    go to zero.
  - They are more than 2s old and a new I/O request comes in on
    the same zfetch.

This means that we will leak unreferenced streams when their zfetch
structure is freed.

This change checks the reference count on a stream at zfetch free
time. If it is zero we free it immediately. If it has remaining
references we allow the prefetch callback to free it at I/O
completion time.

Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Adam Moss <[email protected]>
Reviewed-by: Ryan Moeller <[email protected]>
Signed-off-by: Matt Macy <[email protected]>
Closes openzfs#11052
ghost pushed a commit to zfsonfreebsd/ZoF that referenced this pull request Nov 30, 2020
Currently streams are only freed when:
  - They have no referencing zfetch and and their I/O references
    go to zero.
  - They are more than 2s old and a new I/O request comes in on
    the same zfetch.

This means that we will leak unreferenced streams when their zfetch
structure is freed.

This change checks the reference count on a stream at zfetch free
time. If it is zero we free it immediately. If it has remaining
references we allow the prefetch callback to free it at I/O
completion time.

Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Adam Moss <[email protected]>
Reviewed-by: Ryan Moeller <[email protected]>
Signed-off-by: Matt Macy <[email protected]>
Closes openzfs#11052
ghost pushed a commit to zfsonfreebsd/ZoF that referenced this pull request Dec 1, 2020
Currently streams are only freed when:
  - They have no referencing zfetch and and their I/O references
    go to zero.
  - They are more than 2s old and a new I/O request comes in on
    the same zfetch.

This means that we will leak unreferenced streams when their zfetch
structure is freed.

This change checks the reference count on a stream at zfetch free
time. If it is zero we free it immediately. If it has remaining
references we allow the prefetch callback to free it at I/O
completion time.

Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Adam Moss <[email protected]>
Reviewed-by: Ryan Moeller <[email protected]>
Signed-off-by: Matt Macy <[email protected]>
Closes openzfs#11052
ghost pushed a commit to truenas/zfs that referenced this pull request Jan 6, 2021
Currently streams are only freed when:
  - They have no referencing zfetch and and their I/O references
    go to zero.
  - They are more than 2s old and a new I/O request comes in on
    the same zfetch.

This means that we will leak unreferenced streams when their zfetch
structure is freed.

This change checks the reference count on a stream at zfetch free
time. If it is zero we free it immediately. If it has remaining
references we allow the prefetch callback to free it at I/O
completion time.

Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Adam Moss <[email protected]>
Reviewed-by: Ryan Moeller <[email protected]>
Signed-off-by: Matt Macy <[email protected]>
Closes openzfs#11052
ghost pushed a commit to zfsonfreebsd/ZoF that referenced this pull request Jan 7, 2021
Currently streams are only freed when:
  - They have no referencing zfetch and and their I/O references
    go to zero.
  - They are more than 2s old and a new I/O request comes in on
    the same zfetch.

This means that we will leak unreferenced streams when their zfetch
structure is freed.

This change checks the reference count on a stream at zfetch free
time. If it is zero we free it immediately. If it has remaining
references we allow the prefetch callback to free it at I/O
completion time.

Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Adam Moss <[email protected]>
Reviewed-by: Ryan Moeller <[email protected]>
Signed-off-by: Matt Macy <[email protected]>
Closes openzfs#11052
ghost pushed a commit to truenas/zfs that referenced this pull request Jan 26, 2021
Currently streams are only freed when:
  - They have no referencing zfetch and and their I/O references
    go to zero.
  - They are more than 2s old and a new I/O request comes in on
    the same zfetch.

This means that we will leak unreferenced streams when their zfetch
structure is freed.

This change checks the reference count on a stream at zfetch free
time. If it is zero we free it immediately. If it has remaining
references we allow the prefetch callback to free it at I/O
completion time.

Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Adam Moss <[email protected]>
Reviewed-by: Ryan Moeller <[email protected]>
Signed-off-by: Matt Macy <[email protected]>
Closes openzfs#11052
ghost pushed a commit to truenas/zfs that referenced this pull request Feb 1, 2021
Currently streams are only freed when:
  - They have no referencing zfetch and and their I/O references
    go to zero.
  - They are more than 2s old and a new I/O request comes in on
    the same zfetch.

This means that we will leak unreferenced streams when their zfetch
structure is freed.

This change checks the reference count on a stream at zfetch free
time. If it is zero we free it immediately. If it has remaining
references we allow the prefetch callback to free it at I/O
completion time.

Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Adam Moss <[email protected]>
Reviewed-by: Ryan Moeller <[email protected]>
Signed-off-by: Matt Macy <[email protected]>
Closes openzfs#11052
ghost pushed a commit to zfsonfreebsd/ZoF that referenced this pull request Feb 8, 2021
Currently streams are only freed when:
  - They have no referencing zfetch and and their I/O references
    go to zero.
  - They are more than 2s old and a new I/O request comes in on
    the same zfetch.

This means that we will leak unreferenced streams when their zfetch
structure is freed.

This change checks the reference count on a stream at zfetch free
time. If it is zero we free it immediately. If it has remaining
references we allow the prefetch callback to free it at I/O
completion time.

Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Adam Moss <[email protected]>
Reviewed-by: Ryan Moeller <[email protected]>
Signed-off-by: Matt Macy <[email protected]>
Closes openzfs#11052
ghost pushed a commit to truenas/zfs that referenced this pull request Feb 24, 2021
Currently streams are only freed when:
  - They have no referencing zfetch and and their I/O references
    go to zero.
  - They are more than 2s old and a new I/O request comes in on
    the same zfetch.

This means that we will leak unreferenced streams when their zfetch
structure is freed.

This change checks the reference count on a stream at zfetch free
time. If it is zero we free it immediately. If it has remaining
references we allow the prefetch callback to free it at I/O
completion time.

Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Adam Moss <[email protected]>
Reviewed-by: Ryan Moeller <[email protected]>
Signed-off-by: Matt Macy <[email protected]>
Closes openzfs#11052
ghost pushed a commit to zfsonfreebsd/ZoF that referenced this pull request Mar 5, 2021
Currently streams are only freed when:
  - They have no referencing zfetch and and their I/O references
    go to zero.
  - They are more than 2s old and a new I/O request comes in on
    the same zfetch.

This means that we will leak unreferenced streams when their zfetch
structure is freed.

This change checks the reference count on a stream at zfetch free
time. If it is zero we free it immediately. If it has remaining
references we allow the prefetch callback to free it at I/O
completion time.

Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Adam Moss <[email protected]>
Reviewed-by: Ryan Moeller <[email protected]>
Signed-off-by: Matt Macy <[email protected]>
Closes openzfs#11052
ghost pushed a commit to zfsonfreebsd/ZoF that referenced this pull request Mar 9, 2021
Currently streams are only freed when:
  - They have no referencing zfetch and and their I/O references
    go to zero.
  - They are more than 2s old and a new I/O request comes in on
    the same zfetch.

This means that we will leak unreferenced streams when their zfetch
structure is freed.

This change checks the reference count on a stream at zfetch free
time. If it is zero we free it immediately. If it has remaining
references we allow the prefetch callback to free it at I/O
completion time.

Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Adam Moss <[email protected]>
Reviewed-by: Ryan Moeller <[email protected]>
Signed-off-by: Matt Macy <[email protected]>
Closes openzfs#11052
ghost pushed a commit to truenas/zfs that referenced this pull request Mar 9, 2021
Currently streams are only freed when:
  - They have no referencing zfetch and and their I/O references
    go to zero.
  - They are more than 2s old and a new I/O request comes in on
    the same zfetch.

This means that we will leak unreferenced streams when their zfetch
structure is freed.

This change checks the reference count on a stream at zfetch free
time. If it is zero we free it immediately. If it has remaining
references we allow the prefetch callback to free it at I/O
completion time.

Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Adam Moss <[email protected]>
Reviewed-by: Ryan Moeller <[email protected]>
Signed-off-by: Matt Macy <[email protected]>
Closes openzfs#11052
jsai20 pushed a commit to jsai20/zfs that referenced this pull request Mar 30, 2021
Currently streams are only freed when:
  - They have no referencing zfetch and and their I/O references
    go to zero.
  - They are more than 2s old and a new I/O request comes in on
    the same zfetch.

This means that we will leak unreferenced streams when their zfetch
structure is freed.

This change checks the reference count on a stream at zfetch free
time. If it is zero we free it immediately. If it has remaining
references we allow the prefetch callback to free it at I/O
completion time.

Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Adam Moss <[email protected]>
Reviewed-by: Ryan Moeller <[email protected]>
Signed-off-by: Matt Macy <[email protected]>
Closes openzfs#11052
ghost pushed a commit to truenas/zfs that referenced this pull request May 6, 2021
Currently streams are only freed when:
  - They have no referencing zfetch and and their I/O references
    go to zero.
  - They are more than 2s old and a new I/O request comes in on
    the same zfetch.

This means that we will leak unreferenced streams when their zfetch
structure is freed.

This change checks the reference count on a stream at zfetch free
time. If it is zero we free it immediately. If it has remaining
references we allow the prefetch callback to free it at I/O
completion time.

Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Adam Moss <[email protected]>
Reviewed-by: Ryan Moeller <[email protected]>
Signed-off-by: Matt Macy <[email protected]>
Closes openzfs#11052
ghost pushed a commit to truenas/zfs that referenced this pull request May 19, 2021
Currently streams are only freed when:
  - They have no referencing zfetch and and their I/O references
    go to zero.
  - They are more than 2s old and a new I/O request comes in on
    the same zfetch.

This means that we will leak unreferenced streams when their zfetch
structure is freed.

This change checks the reference count on a stream at zfetch free
time. If it is zero we free it immediately. If it has remaining
references we allow the prefetch callback to free it at I/O
completion time.

Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Adam Moss <[email protected]>
Reviewed-by: Ryan Moeller <[email protected]>
Signed-off-by: Matt Macy <[email protected]>
Closes openzfs#11052
ghost pushed a commit to truenas/zfs that referenced this pull request May 25, 2021
Currently streams are only freed when:
  - They have no referencing zfetch and and their I/O references
    go to zero.
  - They are more than 2s old and a new I/O request comes in on
    the same zfetch.

This means that we will leak unreferenced streams when their zfetch
structure is freed.

This change checks the reference count on a stream at zfetch free
time. If it is zero we free it immediately. If it has remaining
references we allow the prefetch callback to free it at I/O
completion time.

Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Adam Moss <[email protected]>
Reviewed-by: Ryan Moeller <[email protected]>
Signed-off-by: Matt Macy <[email protected]>
Closes openzfs#11052
ghost pushed a commit to truenas/zfs that referenced this pull request May 27, 2021
Currently streams are only freed when:
  - They have no referencing zfetch and and their I/O references
    go to zero.
  - They are more than 2s old and a new I/O request comes in on
    the same zfetch.

This means that we will leak unreferenced streams when their zfetch
structure is freed.

This change checks the reference count on a stream at zfetch free
time. If it is zero we free it immediately. If it has remaining
references we allow the prefetch callback to free it at I/O
completion time.

Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Adam Moss <[email protected]>
Reviewed-by: Ryan Moeller <[email protected]>
Signed-off-by: Matt Macy <[email protected]>
Closes openzfs#11052
sempervictus pushed a commit to sempervictus/zfs that referenced this pull request May 31, 2021
Currently streams are only freed when:
  - They have no referencing zfetch and and their I/O references
    go to zero.
  - They are more than 2s old and a new I/O request comes in on
    the same zfetch.

This means that we will leak unreferenced streams when their zfetch
structure is freed.

This change checks the reference count on a stream at zfetch free
time. If it is zero we free it immediately. If it has remaining
references we allow the prefetch callback to free it at I/O
completion time.

Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Adam Moss <[email protected]>
Reviewed-by: Ryan Moeller <[email protected]>
Signed-off-by: Matt Macy <[email protected]>
Closes openzfs#11052
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Status: Accepted Ready to integrate (reviewed, tested)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants