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

Use Go 1.18 generics in the store’s locking helpers #1577

Merged
merged 7 commits into from
Apr 20, 2023

Conversation

mtrmac
Copy link
Collaborator

@mtrmac mtrmac commented Apr 17, 2023

… avoiding quite a bit of the previously-required boilerplate.

This is probably easier to review as individual commits, and maybe with whitespace changes ignored.

@nalind PTAL

Copy link
Member

@giuseppe giuseppe left a comment

Choose a reason for hiding this comment

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

LGTM

store.go Outdated
@@ -2088,12 +2076,11 @@ func (s *store) ContainerSize(id string) (int64, error) {
return -1, err
}

var res int64 = -1
err = s.writeToContainerStore(func() error { // Yes, s.containerStore.BigDataSize requires a write lock.
return writeToContainerStore(s, func() (int64, error) { // Yes, rcstore.BigDataSize requires a write lock.
Copy link
Member

Choose a reason for hiding this comment

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

Where did rcstore come from?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks, fixed. (It’s a mistake in rebasing, rcstore used to exist back when this branch was originally prepared.)

store.go Outdated
}); done {
return res
return res // false if err != nil
Copy link
Member

Choose a reason for hiding this comment

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

Which err is this referring to, here and in various similar comments where the closure is calling Exists()?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The one that was assigned to _ when calling the locking wrapper…

It would be possible to explain that that in those comments, but at that point it seems simpler to just make the code explicit if err { return false }; if found { return true } now. So updated that way.

@nalind
Copy link
Member

nalind commented Apr 18, 2023

It is, thanks. Generally looks good, though I'm mildly surprised that it doesn't make the logic that much shorter.
Defining a package-local void type when struct{} is widely used just makes me do a double-take when I read it.

@mtrmac
Copy link
Collaborator Author

mtrmac commented Apr 18, 2023

I'm mildly surprised that it doesn't make the logic that much shorter.

I’m afraid that’s just Go. Native error handling and more type inference would be necessary to significantly shorten the boilerplate.

Another modern language:

class Store {
    var containerStore: ContainerStore = ContainerStore()
    
    private func readContainerStore<T>(_ fn: () throws -> T) throws -> T {
        try containerStore.startReading()
        defer {
            containerStore.stopReading()
        }
        return try fn()
    }
    
    func containerBigData(id: String, key: String) throws -> [UInt8] {
        return try readContainerStore {
            return try containerStore.bigData(id: id, key: key)
        }
    }
}

(And actually, with that kind of a language with a working private, we could compile-time enforce that only lock holders can perform operations.

The grass and the other side…)


Defining a package-local void type when struct{} is widely used just makes me do a double-take when I read it.

I chose this because I thought the slight readability benefit, making it very clear that there is nothing there, is worth it — and because I didn’t want to keep typing that struct{}{} — but that’s a very weakly held aesthetic opinion.

I fully realize that this is a fairly goofy approach, and I’m perfectly willing to drop that part.

@mtrmac
Copy link
Collaborator Author

mtrmac commented Apr 18, 2023

I chose this because I thought the slight readability benefit, making it very clear that there is nothing there, is worth it

(Another possible alternative would be to define another set of generic wrappers which don’t simply don’t have that return value, e.g.

func writeToLayerStoreVoid(s *store, fn func(store rwLayerStore) (error)) (error) {
    return writeToLayerStore(s, func(store rwLayerStore) (struct{}, error) {
        return struct{}{}, fn(store)
    })
}

but that feels tedious to me.)

@mtrmac
Copy link
Collaborator Author

mtrmac commented Apr 18, 2023

Updated: I have changed my mind on readContainerStore returning an extra bool. In most callers it causes extra boilerplate, but a few really do need to distinguish between a locking failure, item not found, and valid data. Also the symmetry with the other two read* helpers is a bit easier to follow in the more complex callers.

@nalind
Copy link
Member

nalind commented Apr 18, 2023

Defining a package-local void type when struct{} is widely used just makes me do a double-take when I read it.

I chose this because I thought the slight readability benefit, making it very clear that there is nothing there, is worth it — and because I didn’t want to keep typing that struct{}{} — but that’s a very weakly held aesthetic opinion.

I fully realize that this is a fairly goofy approach, and I’m perfectly willing to drop that part.

Yes, please go ahead and drop that part. I don't have an issue with returning an unused variable from a generic convenience function.

mtrmac added 6 commits April 18, 2023 21:22
Signed-off-by: Miloslav Trmač <[email protected]>
Signed-off-by: Miloslav Trmač <[email protected]>
Signed-off-by: Miloslav Trmač <[email protected]>
Signed-off-by: Miloslav Trmač <[email protected]>
... and use it in many more places.

Signed-off-by: Miloslav Trmač <[email protected]>
@mtrmac
Copy link
Collaborator Author

mtrmac commented Apr 18, 2023

void dropped.

return res, err
if err != nil {
return -1, err
}
Copy link
Member

Choose a reason for hiding this comment

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

If I'm reading it right, err is always nil here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

err can be set if readAllImageStores fails in imageStore.startReading, e.g. on an I/O error.

@nalind
Copy link
Member

nalind commented Apr 18, 2023

One nit in ImageBigDataSize, otherwise LGTM.

@rhatdan
Copy link
Member

rhatdan commented Apr 20, 2023

LGTM

@rhatdan rhatdan merged commit c88992b into containers:main Apr 20, 2023
@mtrmac mtrmac deleted the go1.18 branch April 21, 2023 19:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants