- 
                Notifications
    
You must be signed in to change notification settings  - Fork 3.4k
 
WASMFS chunked fetch backend #23021
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
          
     Merged
      
      
            kripken
  merged 15 commits into
  emscripten-core:main
from
JoeOsborn:wasmfs-chunked-fetch-backend
  
      
      
   
  Feb 10, 2025 
      
    
  
     Merged
                    WASMFS chunked fetch backend #23021
Changes from all commits
      Commits
    
    
            Show all changes
          
          
            15 commits
          
        
        Select commit
          Hold shift + click to select a range
      
      35da69c
              
                chunked backend, works for single chunks and simple backend url maps
              
              
                JoeOsborn 14d419c
              
                add range request support for test web server
              
              
                JoeOsborn 9e8c98d
              
                docs and style fixes, remove traces of manifest idea
              
              
                JoeOsborn 3dc444b
              
                Style improvements
              
              
                JoeOsborn 1a58604
              
                Whitespace fixes
              
              
                JoeOsborn 61b660c
              
                rename fileData->chunks
              
              
                JoeOsborn 4a1b266
              
                Add comments and simplify conditions to clarify chunk boundaries
              
              
                JoeOsborn efb21e4
              
                changelog
              
              
                JoeOsborn 1eee514
              
                refactor test code for wasmfs fetch backend
              
              
                JoeOsborn c602264
              
                add radix to parseInt
              
              
                JoeOsborn e42d8cd
              
                style nits, cleanup
              
              
                JoeOsborn 601d061
              
                Reduce scope of open file in send_head
              
              
                JoeOsborn 808d1e0
              
                Merge branch 'main' into wasmfs-chunked-fetch-backend
              
              
                JoeOsborn c617db2
              
                Update ChangeLog.md
              
              
                kripken e8b5bbf
              
                quote bytes method for closure
              
              
                JoeOsborn File filter
Filter by extension
Conversations
          Failed to load comments.   
        
        
          
      Loading
        
  Jump to
        
          Jump to file
        
      
      
          Failed to load files.   
        
        
          
      Loading
        
  Diff view
Diff view
There are no files selected for viewing
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| 
          
            
          
           | 
    @@ -5,28 +5,24 @@ | |
| */ | ||
| 
     | 
||
| addToLibrary({ | ||
| $wasmFS$JSMemoryRanges: {}, | ||
| 
     | 
||
| // Fetch backend: On first access of the file (either a read or a getSize), it | ||
| // will fetch() the data from the network asynchronously. Otherwise, after | ||
| // that fetch it behaves just like JSFile (and it reuses the code from there). | ||
| 
     | 
||
| _wasmfs_create_fetch_backend_js__deps: [ | ||
| '$wasmFS$backends', | ||
| '$wasmFS$JSMemoryFiles', | ||
| '_wasmfs_create_js_file_backend_js', | ||
| '_wasmfs_fetch_get_file_path', | ||
| '$wasmFS$JSMemoryRanges', | ||
| '_wasmfs_fetch_get_file_url', | ||
| '_wasmfs_fetch_get_chunk_size', | ||
| ], | ||
| _wasmfs_create_fetch_backend_js: async function(backend) { | ||
| // Get a promise that fetches the data and stores it in JS memory (if it has | ||
| // not already been fetched). | ||
| async function getFile(file) { | ||
| if (wasmFS$JSMemoryFiles[file]) { | ||
| // The data is already here, so nothing to do before we continue on to | ||
| // the actual read below. | ||
| return Promise.resolve(); | ||
| } | ||
| // This is the first time we want the file's data. | ||
| async function getFileRange(file, offset, len) { | ||
| var url = ''; | ||
| var fileUrl_p = __wasmfs_fetch_get_file_path(file); | ||
| var fileUrl_p = __wasmfs_fetch_get_file_url(file); | ||
| var fileUrl = UTF8ToString(fileUrl_p); | ||
| var isAbs = fileUrl.indexOf('://') !== -1; | ||
| if (isAbs) { | ||
| 
        
          
        
         | 
    @@ -35,55 +31,127 @@ addToLibrary({ | |
| try { | ||
| var u = new URL(fileUrl, self.location.origin); | ||
| url = u.toString(); | ||
| } catch (e) { | ||
| } catch (_e) { | ||
| throw {status: 404}; | ||
| } | ||
| } | ||
| var response = await fetch(url); | ||
| if (response.ok) { | ||
| var buffer = await response['arrayBuffer'](); | ||
| wasmFS$JSMemoryFiles[file] = new Uint8Array(buffer); | ||
| } else { | ||
| var chunkSize = __wasmfs_fetch_get_chunk_size(file); | ||
| offset ??= 0; | ||
| len ??= chunkSize; | ||
| // In which chunk does the seeked range start? E.g., 5-14 with chunksize 8 will start in chunk 0. | ||
| var firstChunk = (offset / chunkSize) | 0; | ||
| // In which chunk does the seeked range end? E.g., 5-14 with chunksize 8 will end in chunk 1, as will 5-16 (since byte 16 isn't requested). | ||
| // This will always give us a chunk >= firstChunk since len > 0. | ||
| var lastChunk = ((offset+len-1) / chunkSize) | 0; | ||
| if (!(file in wasmFS$JSMemoryRanges)) { | ||
| var fileInfo = await fetch(url, {method:'HEAD', headers:{'Range': 'bytes=0-'}}); | ||
| if (fileInfo.ok && | ||
| fileInfo.headers.has('Content-Length') && | ||
| fileInfo.headers.get('Accept-Ranges') == 'bytes' && | ||
| (parseInt(fileInfo.headers.get('Content-Length'), 10) > chunkSize*2)) { | ||
| wasmFS$JSMemoryRanges[file] = { | ||
| size: parseInt(fileInfo.headers.get('Content-Length'), 10), | ||
| chunks: [], | ||
| chunkSize: chunkSize | ||
| }; | ||
| } else { | ||
| // may as well/forced to download the whole file | ||
| var wholeFileReq = await fetch(url); | ||
| if (!wholeFileReq.ok) { | ||
| throw wholeFileReq; | ||
| } | ||
| var wholeFileData = new Uint8Array(await wholeFileReq.arrayBuffer()); | ||
| var text = new TextDecoder().decode(wholeFileData); | ||
| wasmFS$JSMemoryRanges[file] = { | ||
| size: wholeFileData.byteLength, | ||
| chunks: [wholeFileData], | ||
| chunkSize: wholeFileData.byteLength | ||
| }; | ||
| return Promise.resolve(); | ||
| } | ||
| } | ||
| var allPresent = true; | ||
| var i; | ||
| // Do we have all the chunks already? If so, we don't need to do any fetches. | ||
| for (i = firstChunk; i <= lastChunk; i++) { | ||
| if (!wasmFS$JSMemoryRanges[file].chunks[i]) { | ||
| allPresent = false; | ||
| break; | ||
| } | ||
| } | ||
| if (allPresent) { | ||
| // The data is already here, so nothing to do before we continue on to | ||
| // the actual read. | ||
| return Promise.resolve(); | ||
| } | ||
| // This is the first time we want the chunks' data. We'll make | ||
| // one request for all the chunks we need, rather than one | ||
| // request per chunk. | ||
| var start = firstChunk * chunkSize; | ||
| // We must fetch *up to* the last byte of the last chunk. | ||
| var end = (lastChunk+1) * chunkSize; | ||
| var response = await fetch(url, {headers:{'Range': `bytes=${start}-${end-1}`}}); | ||
| if (!response.ok) { | ||
| throw response; | ||
| } | ||
| var bytes = await response['bytes'](); | ||
| 
         There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm OK landing this this way. I will see if I can find a way to teach closure about this as a followup.  | 
||
| for (i = firstChunk; i <= lastChunk; i++) { | ||
| wasmFS$JSMemoryRanges[file].chunks[i] = bytes.slice(i*chunkSize-start,(i+1)*chunkSize-start); | ||
| } | ||
| return Promise.resolve(); | ||
| } | ||
| 
     | 
||
| // Start with the normal JSFile operations. This sets | ||
| // wasmFS$backends[backend] | ||
| // which we will then augment. | ||
| __wasmfs_create_js_file_backend_js(backend); | ||
| 
     | 
||
| // Add the async operations on top. | ||
| var jsFileOps = wasmFS$backends[backend]; | ||
| wasmFS$backends[backend] = { | ||
| // alloc/free operations are not actually async. Just forward to the | ||
| // parent class, but we must return a Promise as the caller expects. | ||
| allocFile: async (file) => { | ||
| jsFileOps.allocFile(file); | ||
| // nop | ||
| return Promise.resolve(); | ||
| }, | ||
| freeFile: async (file) => { | ||
| jsFileOps.freeFile(file); | ||
| // free memory | ||
| wasmFS$JSMemoryRanges[file] = undefined; | ||
| return Promise.resolve(); | ||
| }, | ||
| 
     | 
||
| write: async (file, buffer, length, offset) => { | ||
| abort("TODO: file writing in fetch backend? read-only for now"); | ||
| console.error('TODO: file writing in fetch backend? read-only for now'); | ||
| }, | ||
| 
     | 
||
| // read/getSize fetch the data, then forward to the parent class. | ||
| read: async (file, buffer, length, offset) => { | ||
| if (length == 0) { | ||
| return 0; | ||
| } | ||
| try { | ||
| await getFile(file); | ||
| } catch (response) { | ||
| return response.status === 404 ? -{{{ cDefs.ENOENT }}} : -{{{ cDefs.EBADF }}}; | ||
| await getFileRange(file, offset || 0, length); | ||
| } catch (failedResponse) { | ||
| return failedResponse.status === 404 ? -{{{ cDefs.ENOENT }}} : -{{{ cDefs.EBADF }}}; | ||
| } | ||
| return jsFileOps.read(file, buffer, length, offset); | ||
| var fileInfo = wasmFS$JSMemoryRanges[file]; | ||
| var chunks = fileInfo.chunks; | ||
| var chunkSize = fileInfo.chunkSize; | ||
| var firstChunk = (offset / chunkSize) | 0; | ||
| // See comments in getFileRange. | ||
| var lastChunk = ((offset+length-1) / chunkSize) | 0; | ||
| var readLength = 0; | ||
| for (var i = firstChunk; i <= lastChunk; i++) { | ||
| var chunk = chunks[i]; | ||
| var start = Math.max(i*chunkSize, offset); | ||
| var chunkStart = i*chunkSize; | ||
| var end = Math.min(chunkStart+chunkSize, offset+length); | ||
| HEAPU8.set(chunk.subarray(start-chunkStart, end-chunkStart), buffer+(start-offset)); | ||
| readLength = end - offset; | ||
| } | ||
| return readLength; | ||
| }, | ||
| getSize: async (file) => { | ||
| try { | ||
| await getFile(file); | ||
| } catch (response) {} | ||
| return jsFileOps.getSize(file); | ||
| await getFileRange(file, 0, 0); | ||
| } catch (failedResponse) { | ||
| return 0; | ||
| } | ||
| return wasmFS$JSMemoryRanges[file].size; | ||
| }, | ||
| }; | ||
| }, | ||
| 
          
            
          
           | 
    ||
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
      
      Oops, something went wrong.
        
    
  
  Add this suggestion to a batch that can be applied as a single commit.
  This suggestion is invalid because no changes were made to the code.
  Suggestions cannot be applied while the pull request is closed.
  Suggestions cannot be applied while viewing a subset of changes.
  Only one suggestion per line can be applied in a batch.
  Add this suggestion to a batch that can be applied as a single commit.
  Applying suggestions on deleted lines is not supported.
  You must change the existing code in this line in order to create a valid suggestion.
  Outdated suggestions cannot be applied.
  This suggestion has been applied or marked resolved.
  Suggestions cannot be applied from pending reviews.
  Suggestions cannot be applied on multi-line comments.
  Suggestions cannot be applied while the pull request is queued to merge.
  Suggestion cannot be applied right now. Please check back later.
  
    
  
    
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this line should've been deleted
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes that’s true, I left it in by mistake. Good catch!