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

Handle Routing Incorrectly Returning DataStore rather than Error #4613

Closed
anthony-murphy opened this issue Dec 16, 2020 · 17 comments
Closed
Labels
area: runtime Runtime related issues bug Something isn't working
Milestone

Comments

@anthony-murphy
Copy link
Contributor

anthony-murphy commented Dec 16, 2020

Trying to resolve a handle to a dds from a key on a map which is expected to fail, but instead returns the handle to the data store. Below is the test

        it.only("Failure to Load in Shared String", async ()=>{
            const deltaConnectionServer = LocalDeltaConnectionServer.create();

            const fluidExport: SupportedExportInterfaces = {
                IFluidDataStoreFactory: new TestFluidObjectFactory([[stringId, SharedString.getFactory()]]),
            };
            const text = "hello world";
            const documentId = "sstest";
            { // creating client
                const urlResolver = new LocalResolver();
                const documentServiceFactory = new LocalDocumentServiceFactory(deltaConnectionServer);
                const codeDetails = { package: "no-dynamic-pkg" };
                const codeLoader = new LocalCodeLoader([
                    [codeDetails, fluidExport],
                ]);

                const loader = new Loader({
                    urlResolver,
                    documentServiceFactory,
                    codeLoader,
                });

                const container = await loader.createDetachedContainer(codeDetails);
                const dataObject = await requestFluidObject<ITestFluidObject>(container, "default");
                const sharedString  = await dataObject.root.get<IFluidHandle<SharedString>>(stringId).get();
                sharedString.insertText(0, text);

                await container.attach(urlResolver.createCreateNewRequest(documentId));
            }
            { // normal load client
                const urlResolver = new LocalResolver();
                const documentServiceFactory = new LocalDocumentServiceFactory(deltaConnectionServer);
                const codeDetails = { package: "no-dynamic-pkg" };
                const codeLoader = new LocalCodeLoader([
                    [codeDetails, fluidExport],
                ]);

                const loader = new Loader({
                    urlResolver,
                    documentServiceFactory,
                    codeLoader,
                });

                const container = await loader.resolve(urlResolver.createCreateNewRequest(documentId));
                const dataObject = await requestFluidObject<ITestFluidObject>(container, "default");
                const sharedString  = await dataObject.root.get<IFluidHandle<SharedString>>(stringId).get();
                assert.strictEqual(sharedString.getText(0), text);
            }
            { // failure load client
                const urlResolver = new LocalResolver();
                const realSf: IDocumentServiceFactory =
                    new LocalDocumentServiceFactory(deltaConnectionServer);
                const documentServiceFactory: IDocumentServiceFactory = {
                    ...realSf,
                    createDocumentService: async (resolvedUrl,logger) => {
                        const realDs = await realSf.createDocumentService(resolvedUrl, logger);
                        const mockDs = Object.create(realDs) as IDocumentService;
                        mockDs.policies = {
                            ... mockDs.policies,
                            caching: LoaderCachingPolicy.NoCaching,
                        };
                        mockDs.connectToStorage = async ()=>{
                            const realStorage = await realDs.connectToStorage();
                            const mockstorage = Object.create(realStorage) as IDocumentStorageService;
                            mockstorage.read = async (id)=>{
                                const blob = await realStorage.read(id);
                                const blobObj = JSON.parse(Buffer.from(blob, "Base64").toString());
                                if (blobObj.chunkStartSegmentIndex !== undefined) {
                                    const error = new NetworkErrorBasic(
                                        "Not Found",
                                        undefined,
                                        false,
                                        404);
                                    throw error;
                                }
                                return blob;
                            };
                            return mockstorage;
                        };
                        return mockDs;
                    },
                };
                const codeDetails = { package: "no-dynamic-pkg" };
                const codeLoader = new LocalCodeLoader([
                    [codeDetails, fluidExport],
                ]);

                const loader = new Loader({
                    urlResolver,
                    documentServiceFactory,
                    codeLoader,
                });

                const container = await loader.resolve(urlResolver.createCreateNewRequest(documentId));
                const dataObject = await requestFluidObject<ITestFluidObject>(container, "default");
                const sharedString  = await dataObject.root.get<IFluidHandle<SharedString>>(stringId).get();
                assert.strictEqual(sharedString.getText(0), text);
            }
        });

@anthony-murphy anthony-murphy added the bug Something isn't working label Dec 16, 2020
@ghost ghost added the triage label Dec 16, 2020
@anthony-murphy
Copy link
Contributor Author

@agarwal-navin and @vladsud

@agarwal-navin
Copy link
Contributor

I can take a look at this. Will have to run the test to see what's happening. Why is handle routing supposed to fail here?

@anthony-murphy
Copy link
Contributor Author

anthony-murphy commented Dec 16, 2020

I found this looking for another issue. That issue is that when the snapshot header request fails for the sequence we are not propagating that, which just lead to an empty sequence. Now I throw from load in the sequence to fail loading. I don't know quite what the right behavior is here, my hunch is that that handle get should also throw, but not sure how that is plumbed.

For the above test, i create a mock for the storage service, and throw when it detects a read on the sequence's header blob (same problem as described above). So when we load the shared string via handle i would expect that failure to bubble up some how

@anthony-murphy
Copy link
Contributor Author

i traced the code a bit, and the load does fail, but then the request handled mixin @vladsud added kicks in, and ends up returning the data store itself

@vladsud
Copy link
Contributor

vladsud commented Dec 16, 2020

I did not read it very carefully, but mixinRequestHandler() is only used when we have external handler, and it just calls it if main request failed with 404. If it converts failure to non-failure, that means the request handler that mixinRequestHandler got does it, i.e. it's in user code.

@anthony-murphy
Copy link
Contributor Author

i didn't look super closely, as trying to figure out the other issue right now. the test repro's it 100%. the last sharedString var will actually be a ITestFluidObject after the handle is retrieved.

@agarwal-navin
Copy link
Contributor

Let me try to repro this and debug.

@agarwal-navin
Copy link
Contributor

I found the issue -
The test is using TestFluidObject which uses mixinRequestHandler to handle requests. When SharedString load fails, data store runtime calls into the TestFluidObject's request handler which just returns itself:

public async request(request: IRequest): Promise<IResponse> {
        return {
            mimeType: "fluid/object",
            status: 200,
            value: this,
        };
    }

Looks like that's the bug. I am not sure why TestFluidObject is using mixin when it doesn't really want to handle requests. We should remove it or handle it appropriately.
I also see lot of examples using mixinRequestHandler similar to TestFluidObject and it does not seem right. We should fix those too.

@vladsud
Copy link
Contributor

vladsud commented Dec 16, 2020

Yes, we have a lot of (mostly sample code, but I saw and fixed similar patterns in shipping code) where objects would not even look at request and return itself. That's pretty much always wrong - objects like that should test for query to be empty.

@anthony-murphy
Copy link
Contributor Author

anthony-murphy commented Dec 16, 2020

When SharedString load fails, data store runtime calls into the TestFluidObject's request handler

I don't really understand how this is even ending up on the request function of the test object, that seems wrong to me, and will likely be unexpected by data store authors.

@anthony-murphy
Copy link
Contributor Author

anthony-murphy commented Dec 16, 2020

I think something like this needs to be done:
main...anthony-murphy:handle-resolution

i think 404 is the wrong error, and is handled at other layers, which is what kicks us down the data object request path. then i think we should throw from the get with the channel resolution failure

@vladsud
Copy link
Contributor

vladsud commented Dec 16, 2020

I'd say that's good change, but it's not enough. Objects should not handle all requests that are coming their way. They should be explicitly handling "/" only in case of terminating node in request tree.
500 is better as it will help in other places. For example (even though not ideal), hosts today repeat request with wait = true if they got 404 with wait = false. So 404 is truly means means not found, not some other error.

@anthony-murphy
Copy link
Contributor Author

anthony-murphy commented Dec 16, 2020

I agree that object shouldn't handle all request, but we also should only route a request to objects that should reasonably expect them. Happy to commit that change if we think it's good enough.

@agarwal-navin
Copy link
Contributor

We also need to fix the request handler in TestFluidObject and other examples. Created #4621 to track this.

@anthony-murphy
Copy link
Contributor Author

image

@anthony-murphy
Copy link
Contributor Author

i'm almost thinking that dataStoreRuntime should mirror container runtime and have a resolveHandle method rather than going down the request flow here

anthony-murphy added a commit that referenced this issue Dec 16, 2020
anthony-murphy added a commit that referenced this issue Dec 17, 2020
Return 500 on channel load failure, rather than 404 which is handled differently throughout the stack

related #4613
anthony-murphy added a commit to anthony-murphy/FluidFramework-1 that referenced this issue Dec 18, 2020
@curtisman curtisman added the area: runtime Runtime related issues label Jan 8, 2021
@curtisman curtisman added this to the January 2021 milestone Jan 8, 2021
@ghost ghost removed the triage label Jan 8, 2021
@anthony-murphy anthony-murphy removed their assignment Jan 11, 2021
@anthony-murphy
Copy link
Contributor Author

closing this as a duplicate of #3062, as the error case is fixed, and #3062 handles the design issue that can lead to this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: runtime Runtime related issues bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants