Skip to content

Add context propagation to rclone #3257

Description

@ajankovic

Add context propagation to rclone

This issue is a proposal to refactor rclone by adopting context.Context propagation in internal usage. rclone was created when this was not a common practice so adopting it would open possibilities for new features, allow it to play better with other Go projects and clear up some technical debt.

Intention is to make only internal structural changes so everything that works and is added today must continue to work once contexts are adopted.

Adoption of contexts will add two main benefits:

  • Ability to propagate cancellation signals, so transfers can be canceled before they are finished
  • Ability to propagate request scoped values through call chains without creating coupling between abstractions

Proof of concept

I've created a fork where big part of this proposal is already implemented (almost all of it). Actual commit where context propagation is addressed can be found here ajankovic@8c7b876

It's a big change for a single commit but it was necessary to keep changes together to avoid breaking the build.

My approach was to first change the base interfaces in fs package and move on from there.I used AWS s3 backend tests as base for deciding to which interface call I should add context. Context spread like virus so it ended up even in some places I haven't initially expected it to go.

After I was satisfied on changes to the interfaces I followed compiler complaints by running make quicktest and making changes until it stopped complaining.

Current state of the POC is that it's working for the use case that I needed it for. I was able to implement Job cancelation and grouping of transferred files by the Job based on it. But it needs review and some design considerations before it can be merged upstream.

Challenge 1: How to refactor fs interfaces?

End result can be seen here ajankovic@8c7b876#diff-f6c6bb8a6ec40b9f63d107bc7070f708

You can use it to follow my explanations for each interface:

fs.Info

I've decided against adding context to fs.Info as it's a interface based around static data.

fs.Fs

Every call needs a context because there is a great possibility that it does interaction with remote service.

fs.Object

Every call needs a context because there is a great possibility that it does interaction with remote service.

fs.ObjectInfo

In my opinion only ObjectInfo.Hash needs context here as it might need to fetch object or request hash from remote.

I wasn't sure so sure about ObjectInfo.Storable because I wasn't sure what it's actual purpose is. I decided against adding context to it. There are some cases where I found that backend needed to do remote call because of it but I think it was the good tradeoff to not add it.

fs.DirEntry

This one was tricky. I decided to only add context to DirEntry.ModTime as it was needed by AWS s3. Later I've seen that some backends need context for DirEntry.Size but I decided against using it there.

I am wondering shouldn't a good practice by backends be to load this information when object is created and serve it statically? Maybe that's not always possible?

fs.MimeTyper

AWS s3 was using it to read metadata so I added it.

fs.Features and others

I followed the same logic, if there is an expected blocking interaction then add context to it. Please take a look at the POC and voice your concerns.

Challenge 2: What to do with tests?

If we take initial constraint of making sure that everything must work like it did without contexts, then adding contexts to tests shouldn't change anything. That means we should just use context.Background() in every place were context is required. That's what I did so all api changes that have existing tests are satisfied with context.Background().

Challenge 3: How to update backends?

Again by the initial constraints backends are only updated by making sure they satisfy modified interfaces. Assumption is that after update, contexts will be available in the function call so it is optional to use it but shouldn't brake existing functionality. Context can be propagated gradually in some later releases as the maintainers see fit.

One special case is AWS s3 backend where propagating was easy (client provides *WithContext replacements) and it was relevant for my particular use case.

Also I've noticed that some backends use clients that don't even have option for propagating context with the request and this will require some additional work later on. So documentation needs to be maintained with information which backend supports this and which does not.

Challenge 4: What to do with NewFs?

It was obvious to me that some remote access initialization is needed for most backends but I figured out this is only needed once and there is no need for actually controlling this process because you need Fs object created. So I just opted for ctx := context.Background() pattern at the top of the NewFs where all calls with context in init will use this context. Maybe this needs change it worked for me.

All other places that I didn't know what context to pass I've used context.TODO().

Challenge 5: How to propagate context to other mechanisms?

All top level functions needed context because it must be propagated to the lower levels. I've also changed rc.Func to accept context. There are some dubious changes to vfs package.

I'll stop here and ask for your review of the changes that are implemented by the ajankovic@8c7b876 commit.

In conclusion

I've tried to make it as generic as possible for the broader rclone usage but I was mainly governed by my own use cases. Please take time to look at it and we can discuss what needs to be done to make it work for the upstream as well.

Backends done

  • alias
  • amazonclouddrive
  • azureblob
  • b2
  • box
  • cache
  • crypt
  • drive
  • dropbox
  • fichier
  • ftp
  • googlecloudstorage
  • googlephotos
  • http
  • hubic
  • jottacloud
  • koofr
  • local
  • mega
  • onedrive
  • opendrive
  • pcloud
  • premiumizeme
  • putio
  • qingstor
  • s3
  • sftp
  • sharefile
  • swift
  • union
  • webdav
  • yandex

Activity

  1. ncw commented on Jun 13, 2019

    @ncw
    Member

    Add context propagation to rclone

    This issue is a proposal to refactor rclone by adopting context.Context propagation in internal usage. rclone was created when this was not a common practice so adopting it would open possibilities for new features, allow it to play better with other Go projects and clear up some technical debt.

    Agreed and thank you very much for stepping up to do this work.

    Intention is to make only internal structural changes so everything that works and is added today must continue to work once contexts are adopted.

    Adoption of contexts will add two main benefits:

    • Ability to propagate cancellation signals, so transfers can be canceled before they are finished
    • Ability to propagate request scoped values through call chains without creating coupling between abstractions

    Agreed.

    Proof of concept

    I've created a fork where big part of this proposal is already implemented (almost all of it). Actual commit where context propagation is addressed can be found here ajankovic@8c7b876

    It's a big change for a single commit but it was necessary to keep changes together to avoid breaking the build.

    My approach was to first change the base interfaces in fs package and move on from there.I used AWS s3 backend tests as base for deciding to which interface call I should add context. Context spread like virus so it ended up even in some places I haven't initially expected it to go.

    After I was satisfied on changes to the interfaces I followed compiler complaints by running make quicktest and making changes until it stopped complaining.

    Current state of the POC is that it's working for the use case that I needed it for. I was able to implement Job cancelation and grouping of transferred files by the Job based on it. But it needs review and some design considerations before it can be merged upstream.

    :-)

    Challenge 1: How to refactor fs interfaces?

    End result can be seen here ajankovic@8c7b876#diff-f6c6bb8a6ec40b9f63d107bc7070f708

    You can use it to follow my explanations for each interface:

    fs.Info

    I've decided against adding context to fs.Info as it's a interface based around static data.

    I think this is fine.

    fs.Fs

    Every call needs a context because there is a great possibility that it does interaction with remote service.

    Agreed

    fs.Object

    Every call needs a context because there is a great possibility that it does interaction with remote service.

    Agreed.

    fs.ObjectInfo

    In my opinion only ObjectInfo.Hash needs context here as it might need to fetch object or request hash from remote.

    I wasn't sure so sure about ObjectInfo.Storable because I wasn't sure what it's actual purpose is. I decided against adding context to it. There are some cases where I found that backend needed to do remote call because of it but I think it was the good tradeoff to not add it.

    Hash/Modtime/MimeType can fetch stuff from the remote but usually won't be.

    Storable() is only really used by the local backend. I think it could probably be got rid of actually!

    fs.DirEntry

    This one was tricky. I decided to only add context to DirEntry.ModTime as it was needed by AWS s3. Later I've seen that some backends need context for DirEntry.Size but I decided against using it there.

    I am wondering shouldn't a good practice by backends be to load this information when object is created and serve it statically? Maybe that's not always possible?

    Some remotes (like s3) store ModTime as metadata which isn't returned in the object listing, so an extra read is needed to fetch it.

    Size() I would have thought should be a constant and stored in the object for just about every backend.

    fs.MimeTyper

    AWS s3 was using it to read metadata so I added it.

    OK

    fs.Features and others

    The optional methods in the Features structs all do stuff - I think you got that right.

    I followed the same logic, if there is an expected blocking interaction then add context to it. Please take a look at the POC and voice your concerns.

    I looked at your changes in fs/fs.go and I think they are good :-)

    Challenge 2: What to do with tests?

    If we take initial constraint of making sure that everything must work like it did without contexts, then adding contexts to tests shouldn't change anything. That means we should just use context.Background() in every place were context is required. That's what I did so all api changes that have existing tests are satisfied with context.Background().

    Great

    Challenge 3: How to update backends?

    Again by the initial constraints backends are only updated by making sure they satisfy modified interfaces. Assumption is that after update, contexts will be available in the function call so it is optional to use it but shouldn't brake existing functionality. Context can be propagated gradually in some later releases as the maintainers see fit.

    One special case is AWS s3 backend where propagating was easy (client provides *WithContext replacements) and it was relevant for my particular use case.

    Also I've noticed that some backends use clients that don't even have option for propagating context with the request and this will require some additional work later on. So documentation needs to be maintained with information which backend supports this and which does not.

    Some of the backends should be easy to update. Anything using lib/rest can be adapted quite easily (with a small patch to lib/rest).

    I think we should change the interfaces for everything first, then update the backends in separate commits just to try to reduce the impact of the first commit.

    Challenge 4: What to do with NewFs?

    It was obvious to me that some remote access initialization is needed for most backends but I figured out this is only needed once and there is no need for actually controlling this process because you need Fs object created. So I just opted for ctx := context.Background() pattern at the top of the NewFs where all calls with context in init will use this context. Maybe this needs change it worked for me.

    NewFs is called from the API too. We should probably add context to it at some point because of that.

    All other places that I didn't know what context to pass I've used context.TODO().

    Great.

    Challenge 5: How to propagate context to other mechanisms?

    All top level functions needed context because it must be propagated to the lower levels. I've also changed rc.Func to accept context. There are some dubious changes to vfs package.

    I'll stop here and ask for your review of the changes that are implemented by the ajankovic@8c7b876 commit.

    I'll run through all the changes in the commit and put notes inline if I see anything.

    In conclusion

    I've tried to make it as generic as possible for the broader rclone usage but I was mainly governed by my own use cases. Please take time to look at it and we can discuss what needs to be done to make it work for the upstream as well.

    Great job :-)

    My aim with this patch is we can get to a point where we can rebase it onto master with no conflicts. This may require a bit of work on your part keeping up with changes in the repo so let's try to keep the momentum going!

    We are in the run-up to a release for v1.48 (at the weekend) so I won't want to merge stuff before then.

  2. ncw commented on Jun 13, 2019

    @ncw
    Member

    I had a look through the code.

    Most of the changes are somewhat mechanical so I skimmed over those.

    I saw the VFS changes. I think that will require more work

    And a thought... Do we need to context-ify methods that don't return an error? If they don't return an error then we can't return the "context cancelled" error?

    So here is what I think we should do (starting after the v1.48 release)

  3. added this to the v1.49 milestone on Jun 13, 2019
  4. ajankovic commented on Jun 19, 2019

    @ajankovic
    ContributorAuthor

    @ncw I've created a PR for this change as per your instructions. We should probably merge it before development for v1.49 speeds up.

  5. ncw commented on Jun 19, 2019

    @ncw
    Member

    I've created a PR for this change as per your instructions. We should probably merge it before development for v1.49 speeds up.

    I've merged that now!

    Next on the list

    • make a second patch for NewFs
      • looking at the code I think it makes sense to do that too
    • patch lib/rest to take a ctx in Call and friends
      https://github.com/ncw/rclone/blob/939b19c3b723fe6c9dd0fd56765dd5b0e404c9ba/lib/rest/rest.go#L184
      • Will be simple to integrate using Request.WithContext
      • this will need patches to the lib/rest using backends too, but should finish contextifying them.
    • make individual patches to context-ify other backends
      • you did this for s3, it is probably possible for more of the other backends but I haven't investigated in detail
    • think about the vfs layer some more!

    Would you like to work on some of these?

    BTW I have an unmerged backend for google photos and it took about 5 minutes to get it compiling again, using compiler error message debugging!

  6. reopened this on Jun 19, 2019
  7. ajankovic commented on Jun 19, 2019

    @ajankovic
    ContributorAuthor

    Yes I could work on first two, and would need help for from you for the last two. Maybe I can also make modifications to the backends that are using clients from the second. The fourth should be done by you as I am overloaded at the moment to understand implications fully.

  8. ncw commented on Jun 19, 2019

    @ncw
    Member

    Yes I could work on first two, and would need help for from you for the last two. Maybe I can also make modifications to the backends that are using clients from the second. The fourth should be done by you as I am overloaded at the moment to understand implications fully.

    That would be great. The first two should be mostly mechanical changes.

    I'm happy to do the third and fourth :-)

  9. added a commit that references this issue on Jun 19, 2019
  10. added 2 commits that reference this issue on Jun 19, 2019
  11. 43 remaining items

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions