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

feat: add project.close() #375

Merged
merged 22 commits into from
Dec 7, 2023
Merged

feat: add project.close() #375

merged 22 commits into from
Dec 7, 2023

Conversation

tomasciccola
Copy link
Contributor

should close #227

I still need to figure out if we should create additional PRs to address closing different parts of a project (i.e. are there parts where we should expose a close() method that hasn't been exposed yet?)

for now:
  close coreManager cores for every namespace
Copy link
Member

@achou11 achou11 left a comment

Choose a reason for hiding this comment

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

Would take a look at the tasks in the related issue as a starting point for things to address in this PR. Non-exhaustive list of tangible changes that probably need to happen:

  • closing the project sqlite instance created in MapeoProject
  • closing the indexer storage and core manager storage created in MapeoProject
  • adding a close() method (or something similar) to the following
    • CoreManager
    • DataStore
    • IndexWriter

Up to you if you want to separate into separate PRs! (probably makes sense to if it isn't too unwieldy)

Also needs tests (probably in e2e?) to test the following, as a minimum:

  • subsequent method calls on the project related to the db/events fail after closing
  • can re-create a project instance after closing it and call relevant methods without issue

src/mapeo-project.js Outdated Show resolved Hide resolved
Tomás Ciccola added 2 commits November 13, 2023 14:22
the close method of `Datastore` basically `.removeAllListeners()` and
`close()` over the `#coreIndexer` instance. I don't if that should be
enough? But looking at the class, it seems so...
@tomasciccola
Copy link
Contributor Author

To close the MulticoreIndexer storage I think we need to change code on the MulticoreIndexer since its the one in charge of instancing a random-access-storage. From what I've seen it would imply exposing the storage field from the CoreIndexStream class and adding a method - maybe .closeStorage or inside the already existing close() method... -

// multicoreIndexer.close()
async close(){
...
  this.#coreIndexStreams.map(indexStream => {
    indexStream.storage.close()
  })
}

In the MulticoreIndexer docs it says that the consumer should be in charge of closing the storage, buut the class itself doesn't expose it and the random-access-storage is actually instanced inside the class, so there's no way I've found two close the storage without changing the class...

@gmaclennan

@gmaclennan
Copy link
Member

Yes this makes sense to me @tomasciccola. I think cleaner to just keep a set of storage instances on the parent class - we create the instance in the "add core" method. Storages need to be closed after the stream is closed (writes happen during the stream destroy).

@tomasciccola
Copy link
Contributor Author

we create the instance in the "add core" method

Its weird that we also create that storage on the constructor (see here).
It seems that the indexStream can have multiple indexStreams (it sound weird, right? but you can see right there a this.#indexStream.addStream(coreIndexStream))

@tomasciccola
Copy link
Contributor Author

I've open an issue here to see approaches of exposing storage on the multi-core-indexer @gmaclennan

@gmaclennan
Copy link
Member

Its weird that we also create that storage on the constructor (see here). It seems that the indexStream can have multiple indexStreams (it sound weird, right? but you can see right there a this.#indexStream.addStream(coreIndexStream))

Since we're indexing multiple cores, there needs to be storage for index state for each core (index state is a bitfield). We create a stream for each core of all data that exists in the core and all data that is added to the core. The multi core index stream is then the merge of all those core index streams. Storage is created in two places because we add cores to the indexer in two places: the cores that exist when we first create the indexer, then any cores that are added after.

Copy link
Member

@gmaclennan gmaclennan left a comment

Choose a reason for hiding this comment

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

left some comments on what is here. I think there is storage that needs closed / cleaned up?

src/mapeo-project.js Outdated Show resolved Hide resolved
src/mapeo-project.js Outdated Show resolved Hide resolved
src/mapeo-project.js Outdated Show resolved Hide resolved
@tomasciccola
Copy link
Contributor Author

I'm struggling to see how to close the coreManager storage from mapeoProject. It seems that we pass and destructure {coreStorage,dbPath} from mapeoManager here, where coreStorage is basically a path that gets to a corestore instance inside coreManager here.
Since we are passing a path, this means that everything gets saved on disc. Is that an random-access-storage instance that we should close() ? Looking at the api of corestore storage is not exposed and looking at the source, it seems that it uses Hypercore.defaultStorage which seems to use random-access-file, but I don't seem to get how to access that to be able to close it (maybe corestore manages closing that?)...
Basically it all comes down to the following:
does calling core.close() means also closing the storage?
@gmaclennan

@gmaclennan
Copy link
Member

src/mapeo-project.js Outdated Show resolved Hide resolved
Copy link
Member

@gmaclennan gmaclennan left a comment

Choose a reason for hiding this comment

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

One small change about removing event listeners - generally removeAllListeners() is a bad idea unless we're absolutely sure nothing else could have attached a listener, because if it has then this can lead to hard-to-track-down bugs. It's better to only remove listeners that a class has added within a class close() method.

I left a note about the tests too. However I don't think it's a priority to get strong test coverage for project.close() at this stage, because it is only used in tests. I would just delete the test that doesn't really test anything.

Also merge conflicts need resolved.

@@ -192,4 +192,9 @@ export class DataStore extends TypedEmitter {
if (!block) throw new Error('Not Found')
return block
}

async close() {
this.#coreIndexer.removeAllListeners()
Copy link
Member

Choose a reason for hiding this comment

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

should only remove listeners that this class has added.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok. The MultiCoreIndexer is already doing that, so I'll just remove that line

Copy link
Member

Choose a reason for hiding this comment

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

added relevant change in 1ad7057 to remove the listeners added to the LocalPeers instance in the constructor. @gmaclennan lmk if this seems like the correct way to go about it

Comment on lines 165 to 173
// close it
await project.close()
// re create project
const newProjectId = await manager.createProject()
const newProject = await manager.getProject(newProjectId)
const newValues = new Array(5).fill(null).map(() => {
return getUpdateFixture(value)
})

Copy link
Member

Choose a reason for hiding this comment

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

This isn't really testing anything - it's closing one project and creating a different one. Is this meant to test closing then re-opening a project? Then it should use the same projectId and check the data that was written before close is still there.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I realize this was the purpose of this tests. But I'm wondering how to achieve that.
If I close a project I'm also closing the storage, and there's not an 'project.open()' or smth similiar. For now I've commented that test...

Copy link
Member

@achou11 achou11 Dec 5, 2023

Choose a reason for hiding this comment

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

@tomasciccola all of the storage instances will be re-created when you call manager.getProject(...) (more specifically when a new mapeo project instance is created), but one thing that needs to be done is removing the cached project instances from the manager (#activeProjects) after closing the project, otherwise the getProject() call will return the instance with all of the closed storages. I have working changes that update the MapeoProject to emit a 'close' event that we use to do this when creating the project instances in the MapeoManager. can push those changes with an updated working test if that's helpful

Copy link
Member

Choose a reason for hiding this comment

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

see 5fee846 for relevant change

Tomás Ciccola added 2 commits December 4, 2023 14:16
  removing added listeners)
* comment crud test regarding closing and re-opening a project
@gmaclennan gmaclennan removed the request for review from achou11 December 7, 2023 04:31
@tomasciccola
Copy link
Contributor Author

Awesome, gonna merge this. thanks @achou11 and @gmaclennan for the help on fixing that!

@tomasciccola tomasciccola merged commit 06a4335 into main Dec 7, 2023
7 checks passed
@achou11 achou11 deleted the feat/projectClose branch December 7, 2023 17:12
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.

Add project.close()
3 participants