├── .gitignore ├── LICENSE ├── src ├── DataBlock.h ├── FileIoServer.h ├── FileIoReadStream_test.cpp ├── FileIoWriteStream_test.cpp ├── SharedBuffer.h ├── SharedBuffer.cpp ├── FileIoStreams.h ├── FileIoRequest.h ├── RecordAndPlayFileMain.cpp ├── FileIoServer.cpp └── FileIoStreams.cpp └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Compiled Dynamic libraries 8 | *.so 9 | *.dylib 10 | *.dll 11 | 12 | # Compiled Static libraries 13 | *.lai 14 | *.la 15 | *.a 16 | *.lib 17 | 18 | # Executables 19 | *.exe 20 | *.out 21 | *.app 22 | 23 | 24 | # User-specific files 25 | *.suo 26 | *.user 27 | *.sln.docstates 28 | 29 | # Visual C++ cache files 30 | ipch/ 31 | *.aps 32 | *.ncb 33 | *.opensdf 34 | *.sdf 35 | *.cachefile 36 | 37 | # Visual C++ Build results 38 | [Dd]ebug/ 39 | [Dd]ebugPublic/ 40 | [Rr]elease/ 41 | x64/ 42 | bld/ 43 | [Bb]in/ 44 | [Oo]bj/ 45 | 46 | *_i.c 47 | *_p.c 48 | *_i.h 49 | *.ilk 50 | *.meta 51 | *.obj 52 | *.pch 53 | *.pdb 54 | *.pgc 55 | *.pgd 56 | *.rsp 57 | *.sbr 58 | *.tlb 59 | *.tli 60 | *.tlh 61 | *.tmp 62 | *.tmp_proj 63 | *.log 64 | *.vspscc 65 | *.vssscc 66 | .builds 67 | *.pidb 68 | *.svclog 69 | *.scc 70 | 71 | 72 | # Ignore Mac specific files 73 | .DS_Store 74 | 75 | # Xcode 76 | 77 | xcuserdata/ 78 | 79 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Ross Bencina 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/DataBlock.h: -------------------------------------------------------------------------------- 1 | /* 2 | Real Time File Streaming copyright (c) 2014 Ross Bencina 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | #ifndef INCLUDED_DATABLOCK_H 23 | #define INCLUDED_DATABLOCK_H 24 | 25 | #include // size_t 26 | 27 | #define IO_DATA_BLOCK_DATA_CAPACITY_BYTES (32*1024) 28 | 29 | struct DataBlock{ 30 | std::size_t capacityBytes; 31 | std::size_t validCountBytes; 32 | void *data; 33 | }; 34 | 35 | #endif /* INCLUDED_DATABLOCK_H */ 36 | 37 | -------------------------------------------------------------------------------- /src/FileIoServer.h: -------------------------------------------------------------------------------- 1 | /* 2 | Real Time File Streaming copyright (c) 2014 Ross Bencina 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | #ifndef INCLUDED_FILEIOSERVER_H 23 | #define INCLUDED_FILEIOSERVER_H 24 | 25 | #include 26 | 27 | #define MAX_FILE_IO_REQUESTS (1024) 28 | 29 | struct FileIoRequest; 30 | 31 | // public interface to the file I/O server: 32 | 33 | // initialize and terminate the server 34 | void startFileIoServer( std::size_t fileIoRequestCount=MAX_FILE_IO_REQUESTS); 35 | void shutDownFileIoServer(); 36 | 37 | // allocate and release requests from the global request pool (real-time safe) 38 | FileIoRequest *allocFileIoRequest(); 39 | void freeFileIoRequest( FileIoRequest *r ); 40 | 41 | // post a request to the server (real-time safe) 42 | void sendFileIoRequestToServer( FileIoRequest *r ); 43 | 44 | // post multiple requests to the server (real-time safe). 45 | // requests should be linked front->b->c->a->back->NULL. 46 | // Back will be the first processed by the server. 47 | void sendFileIoRequestsToServer( FileIoRequest *front, FileIoRequest *back ); 48 | 49 | #endif /* INCLUDED_STREAMINGFILEIO_H */ -------------------------------------------------------------------------------- /src/FileIoReadStream_test.cpp: -------------------------------------------------------------------------------- 1 | #include "FileIoStreams.h" 2 | 3 | #include 4 | #include 5 | 6 | #ifndef WIN32 7 | #include // for usleep 8 | #define Sleep(milliseconds) usleep((milliseconds)*1000) 9 | #endif 10 | 11 | 12 | void FileIoReadStream_test() 13 | { 14 | printf( "> FileIoReadStream_test()\n" ); 15 | 16 | printf( "opening " ); 17 | 18 | // in msvc this is a path relative to the project directory: 19 | #ifdef WIN32 20 | const char *pathString = "..\\..\\..\\src\\FileIoReadStream_test.cpp"; 21 | #else 22 | const char *pathString = "../../../src/FileIoReadStream_test.cpp"; 23 | #endif 24 | SharedBuffer *path = SharedBufferAllocator::alloc(pathString); // print out the source code of this file 25 | 26 | READSTREAM *fp = FileIoReadStream_open(path, FileIoRequest::READ_ONLY_OPEN_MODE); 27 | path->release(); 28 | assert( fp != 0 ); 29 | 30 | while (FileIoReadStream_pollState(fp) == STREAM_STATE_OPENING) { 31 | printf("."); 32 | Sleep(10); 33 | } 34 | 35 | printf( "\ndone.\n" ); 36 | 37 | assert( FileIoReadStream_pollState(fp) == STREAM_STATE_OPEN_IDLE ); 38 | 39 | printf( "seeking " ); 40 | 41 | FileIoReadStream_seek(fp, 0); 42 | 43 | while (FileIoReadStream_pollState(fp) == STREAM_STATE_OPEN_BUFFERING) { 44 | printf("."); 45 | Sleep(10); 46 | } 47 | 48 | assert( FileIoReadStream_pollState(fp) == STREAM_STATE_OPEN_STREAMING ); 49 | 50 | printf( "\ndone.\n" ); 51 | 52 | printf( "reading:\n" ); 53 | 54 | while (FileIoReadStream_pollState(fp) == STREAM_STATE_OPEN_STREAMING || FileIoReadStream_pollState(fp) == STREAM_STATE_OPEN_BUFFERING) { 55 | 56 | /* 57 | // make sure we're always streaming: 58 | while (FileIoReadStream_pollState(fp) == STREAM_STATE_OPEN_BUFFERING) { 59 | printf("."); 60 | Sleep(10); 61 | } 62 | */ 63 | 64 | char c[512]; 65 | size_t bytesToRead = rand() & 0xFF; 66 | size_t bytesRead = FileIoReadStream_read( c, 1, bytesToRead, fp ); 67 | if (bytesRead>0) { 68 | fwrite(c, 1, bytesRead, stdout); 69 | } 70 | } 71 | 72 | assert( FileIoReadStream_pollState(fp) == STREAM_STATE_OPEN_EOF ); 73 | 74 | printf( "\nclosing.\n" ); 75 | 76 | FileIoReadStream_close(fp); 77 | 78 | printf( "< FileIoReadStream_test()\n" ); 79 | } 80 | -------------------------------------------------------------------------------- /src/FileIoWriteStream_test.cpp: -------------------------------------------------------------------------------- 1 | #include "FileIoStreams.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #ifndef WIN32 8 | #include // for usleep 9 | #define Sleep(milliseconds) usleep((milliseconds)*1000) 10 | #endif 11 | 12 | 13 | void FileIoWriteStream_test() 14 | { 15 | printf( "> FileIoWriteStream_test()\n" ); 16 | 17 | printf( "opening " ); 18 | 19 | #ifdef WIN32 20 | const char *testFileName = "..\\..\\..\\FileIoWriteStream_test_output.txt"; 21 | #else 22 | const char *testFileName = "../../../FileIoWriteStream_test_output.txt"; 23 | #endif 24 | // in msvc this is a path relative to the project directory: 25 | SharedBuffer *path = SharedBufferAllocator::alloc(testFileName); 26 | READSTREAM *fp = FileIoWriteStream_open(path, FileIoRequest::READ_WRITE_OVERWRITE_OPEN_MODE); 27 | path->release(); 28 | assert( fp != 0 ); 29 | 30 | while (FileIoWriteStream_pollState(fp) == STREAM_STATE_OPENING) { 31 | printf("."); 32 | Sleep(10); 33 | } 34 | 35 | printf( "\ndone.\n" ); 36 | 37 | assert( FileIoWriteStream_pollState(fp) == STREAM_STATE_OPEN_IDLE ); 38 | 39 | printf( "seeking " ); 40 | 41 | FileIoWriteStream_seek(fp, 0); 42 | 43 | while (FileIoWriteStream_pollState(fp) == STREAM_STATE_OPEN_BUFFERING) { 44 | printf("."); 45 | Sleep(10); 46 | } 47 | 48 | assert( FileIoWriteStream_pollState(fp) == STREAM_STATE_OPEN_STREAMING ); 49 | 50 | printf( "\ndone.\n" ); 51 | 52 | printf( "writing:\n" ); 53 | 54 | const int lineCount = 100000; 55 | 56 | // write 1000 integers in text from 0 to lineCount, one per line 57 | for (int i=0; i < lineCount; ++i) { 58 | 59 | char s[128]; 60 | std::sprintf(s, "%d\n", i); 61 | size_t bytesToWrite = std::strlen(s); 62 | 63 | // write bytes out one at a time because the current implementation of write requires items to be block-aligned 64 | for (size_t j=0; j // max_align_t 26 | 27 | #include "mintomic/mintomic.h" 28 | 29 | /* 30 | Reference counted shared buffer with real-time safe deallocation. 31 | 32 | Used for path strings. 33 | */ 34 | 35 | struct SharedBuffer{ 36 | union{ 37 | mint_atomic32_t refCount; 38 | //std::max_align_t align_; 39 | long double align_; 40 | }; 41 | 42 | char data[1]; 43 | 44 | // the following calls are real-time safe 45 | void addRef() 46 | { 47 | mint_fetch_add_32_relaxed(&refCount,1); 48 | } 49 | 50 | void release(); 51 | }; 52 | 53 | // The following assumes only a single shared buffer allocation domain. 54 | // We may have multiple domains, in which case may be better to abstract 55 | // over an abstract allocator. 56 | 57 | 58 | // None of these methods are real-time safe 59 | struct SharedBufferAllocator { 60 | 61 | static SharedBuffer* alloc( const char *s ); 62 | 63 | // Call this once at the end of the program to ensure that all paths are freed. 64 | // This is called internally whenever you allocate a new shared buffer, 65 | // so you don't need to call it during program execution. 66 | static void reclaimMemory(); 67 | 68 | static void checkForLeaks(); 69 | 70 | private: 71 | friend struct SharedBuffer; 72 | static void enqueueForReclamation( SharedBuffer *p ); // INTERNAL USE 73 | }; 74 | 75 | 76 | inline void SharedBuffer::release() 77 | { 78 | if (mint_fetch_add_32_relaxed(&refCount,-1)==1) 79 | SharedBufferAllocator::enqueueForReclamation(this); 80 | } 81 | 82 | #endif /* INCLUDED_SHAREDBUFFER_H */ 83 | -------------------------------------------------------------------------------- /src/SharedBuffer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Real Time File Streaming copyright (c) 2014 Ross Bencina 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | #include "SharedBuffer.h" 23 | 24 | #include 25 | #include // strcpy 26 | #include 27 | 28 | #include "QwMpmcPopAllLifoStack.h" 29 | 30 | #ifndef NDEBUG 31 | #define DEBUG_COUNT_SHARED_BUFFER_ALLOCATIONS 32 | #endif 33 | 34 | namespace { 35 | 36 | struct MemoryBlock { 37 | MemoryBlock *links_[1]; 38 | }; 39 | 40 | static QwMpmcPopAllLifoStack reclaimQueue_; 41 | 42 | #ifdef DEBUG_COUNT_SHARED_BUFFER_ALLOCATIONS 43 | mint_atomic32_t allocCount_ = {0}; 44 | #endif 45 | 46 | } // end anonymous namespace 47 | 48 | 49 | void SharedBufferAllocator::enqueueForReclamation( SharedBuffer *p ) 50 | { 51 | MemoryBlock *b = reinterpret_cast(p); 52 | b->links_[0] = 0; 53 | reclaimQueue_.push(b); 54 | } 55 | 56 | void SharedBufferAllocator::reclaimMemory() 57 | { 58 | MemoryBlock *b = reclaimQueue_.pop_all(); 59 | 60 | while (b){ 61 | MemoryBlock *k = b; 62 | b = k->links_[0]; 63 | 64 | std::free(k); 65 | 66 | #ifdef DEBUG_COUNT_SHARED_BUFFER_ALLOCATIONS 67 | mint_fetch_add_32_relaxed(&allocCount_,-1); 68 | #endif 69 | } 70 | } 71 | 72 | SharedBuffer* SharedBufferAllocator::alloc( const char *s ) 73 | { 74 | reclaimMemory(); 75 | 76 | SharedBuffer *result = (SharedBuffer*)std::malloc( sizeof(SharedBuffer) + strlen(s) ); 77 | if (result) { 78 | result->refCount._nonatomic = 1; 79 | std::strcpy(result->data, s); 80 | 81 | #ifdef DEBUG_COUNT_SHARED_BUFFER_ALLOCATIONS 82 | mint_fetch_add_32_relaxed(&allocCount_,1); 83 | #endif 84 | } 85 | 86 | return result; 87 | } 88 | 89 | void SharedBufferAllocator::checkForLeaks() 90 | { 91 | #ifdef DEBUG_COUNT_SHARED_BUFFER_ALLOCATIONS 92 | assert( allocCount_._nonatomic == 0 ); 93 | #endif 94 | } -------------------------------------------------------------------------------- /src/FileIoStreams.h: -------------------------------------------------------------------------------- 1 | /* 2 | Real Time File Streaming copyright (c) 2014 Ross Bencina 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | #ifndef INCLUDED_FILEIOSTREAMS_H 23 | #define INCLUDED_FILEIOSTREAMS_H 24 | 25 | #include "FileIoRequest.h" 26 | #include "SharedBuffer.h" 27 | 28 | // NOTE: all functions declared here are real-time safe 29 | 30 | enum FileIoStreamState { 31 | // stream states 32 | 33 | STREAM_STATE_OPENING = FileIoRequest::CLIENT_USE_BASE_, 34 | STREAM_STATE_OPEN_IDLE, 35 | STREAM_STATE_OPEN_EOF, 36 | STREAM_STATE_OPEN_BUFFERING, 37 | STREAM_STATE_OPEN_STREAMING, 38 | STREAM_STATE_ERROR, 39 | }; 40 | 41 | 42 | // read stream 43 | 44 | typedef void READSTREAM; 45 | 46 | READSTREAM *FileIoReadStream_open( SharedBuffer *path, FileIoRequest::OpenMode openMode ); 47 | 48 | void FileIoReadStream_close( READSTREAM *fp ); 49 | 50 | int FileIoReadStream_seek( READSTREAM *fp, size_t pos ); // returns non-zero if there's a problem 51 | 52 | size_t FileIoReadStream_read( void *dest, size_t itemSize, size_t itemCount, READSTREAM *fp ); 53 | 54 | FileIoStreamState FileIoReadStream_pollState( READSTREAM *fp ); 55 | 56 | int FileIoReadStream_getError( READSTREAM *fp ); // returns the error code. only returns non-zero if pollState returns STREAM_STATE_ERROR 57 | 58 | void FileIoReadStream_test(); 59 | 60 | 61 | // write stream 62 | 63 | typedef void WRITESTREAM; 64 | 65 | WRITESTREAM *FileIoWriteStream_open( SharedBuffer *path, FileIoRequest::OpenMode openMode ); 66 | 67 | void FileIoWriteStream_close( WRITESTREAM *fp ); 68 | 69 | int FileIoWriteStream_seek( WRITESTREAM *fp, size_t pos ); // returns non-zero if there's a problem 70 | 71 | size_t FileIoWriteStream_write( const void *src, size_t itemSize, size_t itemCount, WRITESTREAM *fp ); 72 | 73 | FileIoStreamState FileIoWriteStream_pollState( WRITESTREAM *fp ); 74 | 75 | int FileIoWriteStream_getError( WRITESTREAM *fp ); // returns the error code. only returns non-zero if pollState returns STREAM_STATE_ERROR 76 | 77 | void FileIoWriteStream_test(); 78 | 79 | 80 | #endif /* INCLUDED_FILEIOSTREAMS_H */ 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RealTimeFileStreaming 2 | ===================== 3 | 4 | Example of interfacing PortAudio real time audio with file I/O 5 | 6 | This is example code for the paper: 7 | 8 | > Bencina, R. (2014) "Interfacing Real-Time Audio and File I/O," Proceedings of the 2014 Australasian Computer Music Conference, ACMC 2014. Melbourne, Australia. July 9-11 2014. 9 | 10 | The paper and supporting materials are available at the following URL: 11 | 12 | http://www.rossbencina.com/code/interfacing-real-time-audio-and-file-io 13 | 14 | Status 15 | ------ 16 | 17 | The code works on OS X and Windows. It has been tested on Windows 7 with MSVC10 and OS X 10.7.5 with Xcode 4.6.1. 18 | 19 | There's a little bit of work to do to get it working on Linux (replace Mach semaphore_t with sem_t). Maybe a bit more work to get it running on ARM (the interlocked exchange atomic primitive might need some work, not sure). 20 | 21 | 22 | Source code overview 23 | -------------------- 24 | 25 | `FileIoStreams.h/.cpp` client stream objects for streaming data to or from a file. Lock-free and real-time safe. 26 | 27 | `FileIoRequest.h` asynchronous message node object. Represents requests to, and replies from, the I/O server thread. 28 | 29 | `FileIoServer.h/.cpp` file I/O server thread. Responds to FileIoRequests from client streams. 30 | 31 | `DataBlock.h` buffer descriptor. Represents blocks of data read/written from/to a file. Pointers to DataBlocks are passed between server and client in FileIoRequest messages. 32 | 33 | `SharedBuffer.h/.cpp` reference counted immutable shared buffer with lock-free cleanup. Used for storing file paths. 34 | 35 | `RecordAndPlayFileMain.cpp` example real-time audio program that records and plays raw 16-bit stereo files. 36 | 37 | 38 | 39 | How to build and run the example 40 | -------------------------------- 41 | 42 | *At the moment there are project files for Windows MSVC10 and OS X (Xcode 4.6 or later). Help with Linux would be welcome.* 43 | 44 | 1. Check out the sources and the dependencies: 45 | 46 | ``` 47 | git clone https://github.com/RossBencina/RealTimeFileStreaming.git 48 | git clone -b C++03-legacy https://github.com/RossBencina/QueueWorld.git 49 | git clone https://github.com/mintomic/mintomic.git 50 | svn co https://subversion.assembla.com/svn/portaudio/portaudio/trunk/ portaudio 51 | ``` 52 | 53 | You should now have the following directories: 54 | 55 | ``` 56 | RealTimeFileStreaming/ 57 | QueueWorld/ 58 | mintomic/ 59 | portaudio/ 60 | ``` 61 | 62 | [Note: this example code uses the C++03 version of QueueWorld. The C++11 version of QueueWorld is currently untested with this RealTimeFileStreaming example code.] 63 | 64 | 2. Open the project. 65 | - On Windows, with MSVC2010 or later, navigate to `RealTimeFileStreaming\build\msvs10\RealTimeFileStreaming` and open the Visual Studio solution file `RealTimeFileStreaming.sln` 66 | - On OS X, with Xcode 4.6 or later, navigate to `RealTimeFileStreaming\build\xcode4.6\RealTimeFileStreaming` and open the Xcode project `RealTimeFileStreaming.xcodeproj` 67 | 68 | 69 | 3. Set up audio file paths. The example program references two file paths: `playbackFilePath` for playing an existing file and `recordTestFilePath` for playing and recording a test file. These are both declared in `main()` in `RecordAndPlayFileMain.cpp`. You need to edit their values to refer to valid file paths on your system (don't forget to escape backslashes if you're on Windows). The example code only reads headerless 16-bit stereo files (44.1k for the default settings). 70 | - For `playbackFilePath` you either need to create a file of the appropriate format, or you can grab this file: https://www.dropbox.com/s/ec923lzkr9udxww/171326__bradovic__piano-improvisation.dat [1]. 71 | - The `recordTestFilePath` file should be a valid path on your system, but the file should *not* exist. It will be created or overwritten every time you start recording from within the test program. Just make sure that the directory portion of the path exists. 72 | 73 | 74 | 4. Run the project. It should build and run, playing a sine wave. There are instructions on the screen for starting and stopping recording and playback. It will work for recording and playing a new test file (`recordTestFilePath`) even if you don't have the `playbackFilePath` file set up correctly. 75 | 76 | 77 | [1] File credit: Bradovic at freesound.org https://www.freesound.org/people/Bradovic/sounds/171326/ (CC-Zero). 78 | -------------------------------------------------------------------------------- /src/FileIoRequest.h: -------------------------------------------------------------------------------- 1 | /* 2 | Real Time File Streaming copyright (c) 2014 Ross Bencina 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | #ifndef INCLUDED_FILEIOREQUEST_H 23 | #define INCLUDED_FILEIOREQUEST_H 24 | 25 | #include // size_t 26 | 27 | #include "QwSpscUnorderedResultQueue.h" 28 | #include "SharedBuffer.h" 29 | 30 | #define IO_INVALID_FILE_HANDLE (0) 31 | 32 | struct DataBlock; 33 | struct QwSharedBuffer; 34 | 35 | struct FileIoRequest{ 36 | enum LinkIndices{ 37 | TRANSIT_NEXT_LINK_INDEX = 0, 38 | CLIENT_NEXT_LINK_INDEX = 1, 39 | LINK_COUNT=2 40 | }; 41 | FileIoRequest* links_[LINK_COUNT]; 42 | 43 | enum RequestType { 44 | OPEN_FILE, 45 | CLOSE_FILE, 46 | READ_BLOCK, 47 | RELEASE_READ_BLOCK, 48 | ALLOCATE_WRITE_BLOCK, 49 | COMMIT_MODIFIED_WRITE_BLOCK, 50 | RELEASE_UNMODIFIED_WRITE_BLOCK, 51 | CLEANUP_RESULT_QUEUE, 52 | RESULT_QUEUE_IS_AWAITING_CLEANUP_, /* SERVER INTERNAL USE ONLY */ 53 | 54 | CLIENT_USE_BASE_ = 16 55 | }; 56 | 57 | enum OpenMode { 58 | READ_ONLY_OPEN_MODE, 59 | READ_WRITE_OVERWRITE_OPEN_MODE 60 | }; 61 | 62 | int resultStatus; // an ERRNO value 63 | 64 | union { 65 | size_t clientInt; 66 | void *clientPtr; 67 | }; 68 | 69 | // result queue type, used below: 70 | typedef QwSpscUnorderedResultQueue result_queue_t; 71 | 72 | // discriminated union: 73 | int requestType; // RequestType 74 | union { 75 | 76 | /* OPEN_FILE */ 77 | struct { 78 | SharedBuffer *path; // IN NOTE: request owns a ref to path (use addRef and release) 79 | OpenMode openMode; // IN 80 | void *fileHandle; // OUT 81 | FileIoRequest *resultQueue; // IN 82 | } openFile; 83 | 84 | /* CLOSE_FILE */ 85 | struct { 86 | void *fileHandle; // IN 87 | } closeFile; 88 | 89 | /* READ_BLOCK */ 90 | struct { 91 | void *fileHandle; // IN 92 | std::size_t filePosition; // IN 93 | DataBlock *dataBlock; // OUT 94 | bool isAtEof; // OUT 95 | FileIoRequest *resultQueue; // IN 96 | } readBlock; 97 | 98 | /* RELEASE_READ_BLOCK */ 99 | struct { 100 | void *fileHandle; // IN 101 | DataBlock *dataBlock; // IN 102 | } releaseReadBlock; 103 | 104 | /* ALLOCATE_WRITE_BLOCK */ 105 | struct { 106 | void *fileHandle; // IN 107 | std::size_t filePosition; // IN 108 | DataBlock *dataBlock; // OUT 109 | FileIoRequest *resultQueue; // IN 110 | } allocateWriteBlock; 111 | 112 | /* COMMIT_MODIFIED_WRITE_BLOCK */ 113 | struct { 114 | void *fileHandle; // IN 115 | std::size_t filePosition; // IN 116 | DataBlock *dataBlock; // IN 117 | } commitModifiedWriteBlock; 118 | 119 | /* RELEASE_UNMODIFIED_WRITE_BLOCK */ 120 | struct { 121 | void *fileHandle; // IN 122 | DataBlock *dataBlock; // IN 123 | } releaseUnmodifiedWriteBlock; 124 | 125 | /* CLEANUP_RESULT_QUEUE */ 126 | result_queue_t resultQueue; 127 | }; 128 | }; 129 | 130 | #endif /* INCLUDED_FILEIOREQUEST_H */ 131 | -------------------------------------------------------------------------------- /src/RecordAndPlayFileMain.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Real Time File Streaming copyright (c) 2014 Ross Bencina 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | #include 23 | #include 24 | #include "portaudio.h" 25 | 26 | #include "FileIoServer.h" 27 | #include "FileIoStreams.h" 28 | #include "QwMpscFifoQueue.h" 29 | 30 | #define SAMPLE_RATE (44100) 31 | //#define FRAMES_PER_BUFFER (64) 32 | #define FRAMES_PER_BUFFER (67) // use a prime number to flush out bugs in buffer copying code 33 | #ifndef M_PI 34 | #define M_PI (3.14159265) 35 | #endif 36 | 37 | 38 | struct ControlCommand { 39 | enum LinkIndices{ 40 | TRANSIT_NEXT_LINK_INDEX = 0, 41 | LINK_COUNT=1 42 | }; 43 | ControlCommand *links_[LINK_COUNT]; 44 | 45 | enum CommandType { 46 | START_PLAYING, 47 | STOP_PLAYING, 48 | START_RECORDING, 49 | STOP_RECORDING 50 | }; 51 | 52 | int commandType; 53 | 54 | SharedBuffer *filePath; // 0 if unused 55 | 56 | // We'll allocate and destroy commands in the main thread. 57 | // We don't need construction or destruction to be real-time. 58 | 59 | ControlCommand( int commandType_, const char *filePathCStr=0 ) 60 | : commandType( commandType_ ) 61 | , filePath( 0 ) 62 | { 63 | for (int i=0; i < LINK_COUNT; ++i) 64 | links_[i] = 0; 65 | 66 | // construct shared buffer in the non-real-time context 67 | if (filePathCStr) 68 | filePath = SharedBufferAllocator::alloc(filePathCStr); 69 | } 70 | 71 | ~ControlCommand() 72 | { 73 | if (filePath) { 74 | filePath->release(); 75 | filePath = 0; 76 | } 77 | } 78 | }; 79 | 80 | typedef QwMpscFifoQueue control_command_queue_t; 81 | 82 | 83 | #define TABLE_SIZE (200) 84 | typedef struct 85 | { 86 | // sine oscillator so we can hear whether the stream is glitching 87 | float sine[TABLE_SIZE]; 88 | int left_phase; 89 | int right_phase; 90 | 91 | // 92 | 93 | control_command_queue_t commandsToAudioCallback; 94 | control_command_queue_t commandsFromAudioCallback; 95 | 96 | READSTREAM *playStream; 97 | WRITESTREAM *recordStream; 98 | } 99 | TestStreamCallbackData; 100 | 101 | /* This routine will be called by the PortAudio engine when audio is needed. 102 | ** It may called at interrupt level on some machines so don't do anything 103 | ** that could mess up the system like calling malloc() or free(). 104 | */ 105 | static int audioCallback( const void *inputBuffer, void *outputBuffer, 106 | unsigned long framesPerBuffer, 107 | const PaStreamCallbackTimeInfo* timeInfo, 108 | PaStreamCallbackFlags statusFlags, 109 | void *userData ) 110 | { 111 | TestStreamCallbackData *data = (TestStreamCallbackData*)userData; 112 | float *out = (float*)outputBuffer; // interleaved left and right channels 113 | unsigned long i; 114 | 115 | (void) timeInfo; /* Prevent unused variable warnings. */ 116 | (void) statusFlags; 117 | (void) inputBuffer; 118 | 119 | // output a sine tone when we're not playing 120 | float sineGain = 0.1f; // quiet 121 | for( i=0; isine[data->left_phase] * sineGain; /* left */ 124 | *out++ = data->sine[data->right_phase] * sineGain; /* right */ 125 | data->left_phase += 1; 126 | if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE; 127 | data->right_phase += 3; /* higher pitch so we can distinguish left and right. */ 128 | if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE; 129 | } 130 | 131 | 132 | // receive commands from the main thread. demostrates creating and destroying streams in the real-time thread: 133 | 134 | while (ControlCommand *cmd = data->commandsToAudioCallback.pop()) { 135 | switch (cmd->commandType) { 136 | case ControlCommand::START_PLAYING: 137 | assert( !data->playStream ); 138 | data->playStream = FileIoReadStream_open(cmd->filePath, FileIoRequest::READ_ONLY_OPEN_MODE); 139 | break; 140 | case ControlCommand::STOP_PLAYING: 141 | if (data->playStream) { // idempotent to simplify logic in main() 142 | FileIoReadStream_close(data->playStream); 143 | data->playStream = 0; 144 | } 145 | break; 146 | case ControlCommand::START_RECORDING: 147 | assert( !data->recordStream ); 148 | data->recordStream = FileIoWriteStream_open(cmd->filePath, FileIoRequest::READ_WRITE_OVERWRITE_OPEN_MODE); 149 | break; 150 | case ControlCommand::STOP_RECORDING: 151 | if (data->recordStream) { // idempotent to simplify logic in main() 152 | FileIoWriteStream_close(data->recordStream); 153 | data->recordStream = 0; 154 | } 155 | break; 156 | } 157 | 158 | data->commandsFromAudioCallback.push(cmd); // return command to main thread for deletion 159 | } 160 | 161 | // looped playback of the playStream, if it exists 162 | if (data->playStream) { 163 | FileIoStreamState streamState = FileIoReadStream_pollState(data->playStream); 164 | if (streamState==STREAM_STATE_OPEN_IDLE || streamState==STREAM_STATE_OPEN_EOF) { 165 | 166 | // Once the file is open, seek to play from start. also, when it reaches EOF, loop 167 | FileIoReadStream_seek(data->playStream,0); 168 | 169 | } else if (streamState==STREAM_STATE_OPEN_STREAMING) { // (don't output in BUFFERING state) 170 | 171 | short temp[FRAMES_PER_BUFFER * 2]; // stereo 16 bit file data 172 | size_t framesRead = FileIoReadStream_read( temp, sizeof(short)*2, FRAMES_PER_BUFFER, data->playStream ); 173 | 174 | // Copy data to output 175 | float gain = 1./32768; 176 | 177 | out = (float*)outputBuffer; 178 | const short *p = &temp[0]; 179 | for( size_t i=0; i < framesRead; ++i ) { 180 | *out++ = *p++ * gain; 181 | *out++ = *p++ * gain; 182 | } 183 | } 184 | } 185 | 186 | if (data->recordStream) { 187 | FileIoStreamState streamState = FileIoWriteStream_pollState(data->recordStream); 188 | if (streamState==STREAM_STATE_OPEN_IDLE) { 189 | 190 | // Once the file is open, seek to record from start. This allocates buffers. 191 | FileIoWriteStream_seek(data->recordStream,0); 192 | 193 | } else if (streamState==STREAM_STATE_OPEN_STREAMING) { // (don't record in BUFFERING state) 194 | short temp[FRAMES_PER_BUFFER * 2]; // stereo 16 bit file data 195 | 196 | // Copy data from input 197 | float gain = 32767; 198 | 199 | float *in = (float*)inputBuffer; 200 | short *p = &temp[0]; 201 | for( size_t i=0; i < FRAMES_PER_BUFFER; ++i ) { 202 | *p++ = (short)(*in++ * gain); 203 | *p++ = (short)(*in++ * gain); 204 | } 205 | 206 | FileIoWriteStream_write( temp, sizeof(short)*2, FRAMES_PER_BUFFER, data->recordStream ); 207 | } 208 | } 209 | 210 | return paContinue; 211 | } 212 | 213 | 214 | /*******************************************************************/ 215 | 216 | void fail( int err ) 217 | { 218 | Pa_Terminate(); 219 | 220 | shutDownFileIoServer(); 221 | SharedBufferAllocator::reclaimMemory(); 222 | 223 | fprintf( stderr, "An error occured while using the portaudio stream\n" ); 224 | fprintf( stderr, "Error number: %d\n", err ); 225 | fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); 226 | exit(err); 227 | } 228 | 229 | int main(int argc, char *argv[]); 230 | int main(int argc, char *argv[]) 231 | { 232 | PaError err; 233 | TestStreamCallbackData paStreamData; 234 | 235 | /* initialise sinusoidal wavetable */ 236 | for( int i=0; idefaultLowOutputLatency; 274 | inputParameters.hostApiSpecificStreamInfo = NULL; 275 | 276 | PaStreamParameters outputParameters; 277 | outputParameters.device = Pa_GetDefaultOutputDevice(); 278 | if (outputParameters.device == paNoDevice) { 279 | fprintf(stderr,"Error: No default output device.\n"); 280 | err = paInvalidDevice; 281 | fail(err); 282 | } 283 | outputParameters.channelCount = 2; /* stereo output */ 284 | outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */ 285 | outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; 286 | outputParameters.hostApiSpecificStreamInfo = NULL; 287 | 288 | PaStream *stream; 289 | err = Pa_OpenStream( 290 | &stream, 291 | &inputParameters, 292 | &outputParameters, 293 | SAMPLE_RATE, 294 | FRAMES_PER_BUFFER, 295 | paNoFlag, 296 | audioCallback, 297 | &paStreamData ); 298 | if( err != paNoError ) fail(err); 299 | 300 | err = Pa_StartStream( stream ); 301 | if( err != paNoError ) fail(err); 302 | 303 | // process user input... 304 | 305 | printf("Press p+[Enter] to toggle playing the playback file (%s).\n", playbackFilePath); 306 | printf("Press r+[Enter] to toggle recording the test file (%s).\n", recordTestFilePath); 307 | printf("Press t+[Enter] to toggle playing the test file (%s).\n", recordTestFilePath); 308 | printf("Press q+[Enter] to quit.\n"); 309 | 310 | enum State { IDLE, PLAYING_PLAYBACK_FILE, RECORDING_TEST_FILE, PLAYING_TEST_FILE }; 311 | State state = IDLE; 312 | 313 | bool quit=false; 314 | while(!quit) { 315 | int c = getc(stdin); 316 | if (c =='q') { 317 | quit=true; 318 | 319 | } else if (c=='p') { // play or stop playing the playback file 320 | if (state==PLAYING_PLAYBACK_FILE) { 321 | printf("STOP playing playback file (stopping)\n"); 322 | paStreamData.commandsToAudioCallback.push(new ControlCommand(ControlCommand::STOP_PLAYING)); 323 | state = IDLE; 324 | } else { 325 | printf("PLAY playback file (starting)\n"); 326 | paStreamData.commandsToAudioCallback.push(new ControlCommand(ControlCommand::STOP_PLAYING)); 327 | paStreamData.commandsToAudioCallback.push(new ControlCommand(ControlCommand::STOP_RECORDING)); 328 | paStreamData.commandsToAudioCallback.push(new ControlCommand(ControlCommand::START_PLAYING, playbackFilePath)); 329 | state = PLAYING_PLAYBACK_FILE; 330 | } 331 | 332 | } else if (c=='r') { 333 | if (state==RECORDING_TEST_FILE) { 334 | printf("STOP recording test-recording file (stopping)\n"); 335 | paStreamData.commandsToAudioCallback.push(new ControlCommand(ControlCommand::STOP_RECORDING)); 336 | state = IDLE; 337 | } else { 338 | printf("RECORD test-recording file (starting)\n"); 339 | paStreamData.commandsToAudioCallback.push(new ControlCommand(ControlCommand::STOP_PLAYING)); 340 | paStreamData.commandsToAudioCallback.push(new ControlCommand(ControlCommand::STOP_PLAYING)); 341 | paStreamData.commandsToAudioCallback.push(new ControlCommand(ControlCommand::START_RECORDING, recordTestFilePath)); 342 | state = RECORDING_TEST_FILE; 343 | } 344 | 345 | } else if (c=='t') { 346 | if (state==PLAYING_TEST_FILE) { 347 | printf("STOP playing test-recording file (stopping)\n"); 348 | paStreamData.commandsToAudioCallback.push(new ControlCommand(ControlCommand::STOP_PLAYING)); 349 | state = IDLE; 350 | } else { 351 | printf("PLAY test-recording file (starting)\n"); 352 | paStreamData.commandsToAudioCallback.push(new ControlCommand(ControlCommand::STOP_PLAYING)); 353 | paStreamData.commandsToAudioCallback.push(new ControlCommand(ControlCommand::STOP_RECORDING)); 354 | paStreamData.commandsToAudioCallback.push(new ControlCommand(ControlCommand::START_PLAYING, recordTestFilePath)); 355 | state = PLAYING_TEST_FILE; 356 | } 357 | } 358 | 359 | // reclaim used commands 360 | while (ControlCommand *cmd = paStreamData.commandsFromAudioCallback.pop()) 361 | delete cmd; 362 | } 363 | 364 | err = Pa_StopStream( stream ); 365 | if( err != paNoError ) fail(err); 366 | 367 | err = Pa_CloseStream( stream ); 368 | if( err != paNoError ) fail(err); 369 | 370 | Pa_Terminate(); 371 | 372 | // reclaim used commands 373 | while (ControlCommand *cmd = paStreamData.commandsFromAudioCallback.pop()) 374 | delete cmd; 375 | 376 | if (paStreamData.playStream) { 377 | FileIoReadStream_close(paStreamData.playStream); 378 | paStreamData.playStream = 0; 379 | } 380 | 381 | if (paStreamData.recordStream) { 382 | FileIoWriteStream_close(paStreamData.recordStream); 383 | paStreamData.recordStream = 0; 384 | } 385 | 386 | shutDownFileIoServer(); 387 | SharedBufferAllocator::reclaimMemory(); 388 | SharedBufferAllocator::checkForLeaks(); 389 | 390 | printf("Test finished.\n"); 391 | 392 | return 0; 393 | } 394 | 395 | 396 | 397 | /* 398 | TODO: 399 | 400 | ---------------------- 401 | 402 | another demo would be a multi-file-player demo: 403 | 404 | basically it can play multiple files. you queue them up from the main thread by typing keys a, b, c, d etc. 405 | It keeps the PA stream open while there is at least one file playing, but it can play more than one at once. 406 | 407 | ---------------------- 408 | 409 | */ -------------------------------------------------------------------------------- /src/FileIoServer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Real Time File Streaming copyright (c) 2014 Ross Bencina 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | #include "FileIoServer.h" 23 | 24 | #include 25 | #include 26 | 27 | #ifdef WIN32 28 | #define NOMINMAX // suppress windows.h min/max 29 | #include 30 | #include 31 | #include 32 | #else 33 | #include 34 | #include 35 | #include // semaphore_create/destroy 36 | #include // semaphore_signal, semaphore_wait 37 | #endif 38 | 39 | #ifndef NOERROR 40 | #define NOERROR (0) 41 | #endif 42 | 43 | #include "QwMpscFifoQueue.h" 44 | #include "QwNodePool.h" 45 | #include "QwSpscUnorderedResultQueue.h" 46 | #include "SharedBuffer.h" 47 | #include "DataBlock.h" 48 | 49 | #include "FileIoRequest.h" 50 | 51 | /////////////////////////////////////////////////////////////////////////////// 52 | // FileIoRequest allocation 53 | 54 | static QwNodePool *globalRequestPool_ = 0; // managed by startFileIoServer/shutDownFileIoServer 55 | 56 | FileIoRequest *allocFileIoRequest() 57 | { 58 | return globalRequestPool_->allocate(); 59 | } 60 | 61 | void freeFileIoRequest( FileIoRequest *r ) 62 | { 63 | globalRequestPool_->deallocate(r); 64 | } 65 | 66 | /////////////////////////////////////////////////////////////////////////////// 67 | // Server thread routines 68 | 69 | static void cleanupOneRequestResult( FileIoRequest *r ); // forward reference 70 | 71 | static void completeRequestToClientResultQueue( FileIoRequest *clientResultQueueContainer, FileIoRequest *r ) 72 | { 73 | // Poll the state of the result queue *before* posting result back to client 74 | // because if the result queue is not being cleaned up the server doesn't own it 75 | // and can't use it after returning the result. 76 | bool resultQueueIsAwaitingCleanup = (clientResultQueueContainer->requestType == FileIoRequest::RESULT_QUEUE_IS_AWAITING_CLEANUP_); 77 | 78 | if (resultQueueIsAwaitingCleanup) { 79 | // Option A: 80 | cleanupOneRequestResult(r); 81 | if (clientResultQueueContainer->resultQueue.expectedResultCount() == 0) 82 | freeFileIoRequest(clientResultQueueContainer); 83 | 84 | /* 85 | // Option B: 86 | // the potential advantage of doing it this way is that the cleanup is deferred until 87 | // after all currently pending requests have been handled. this might reduce request 88 | // handling latency slightly, at the expense of leaving unused resources allocated longer. 89 | pushResultToResultQueue(clientResultQueueContainer, r); 90 | 91 | // schedule the result queue for further cleanup 92 | clientResultQueueContainer->requestType = FileIoRequest::CLEANUP_RESULT_QUEUE; 93 | pushServerInternalFileIoRequest( clientResultQueueContainer ); 94 | */ 95 | 96 | } else { 97 | clientResultQueueContainer->resultQueue.push(r); 98 | } 99 | } 100 | 101 | /// 102 | 103 | static DataBlock* allocDataBlock() 104 | { 105 | DataBlock *result = new DataBlock; 106 | assert( result ); 107 | result->capacityBytes = IO_DATA_BLOCK_DATA_CAPACITY_BYTES; 108 | result->validCountBytes = 0; 109 | result->data = new int8_t[ IO_DATA_BLOCK_DATA_CAPACITY_BYTES ]; 110 | assert( result->data ); 111 | return result; 112 | } 113 | 114 | static void freeDataBlock( DataBlock *b ) 115 | { 116 | delete [] (int8_t*)b->data; 117 | delete b; 118 | } 119 | 120 | namespace { 121 | struct FileRecord{ 122 | FILE *fp; 123 | int dependentClientCount; 124 | }; 125 | } // end anonymous namespace 126 | 127 | static void handleOpenFileRequest( FileIoRequest *r ) 128 | { 129 | assert( r->requestType == FileIoRequest::OPEN_FILE ); 130 | 131 | FileRecord *fileRecord = new FileRecord; 132 | if (fileRecord) { 133 | const char *fopenMode = (r->openFile.openMode == FileIoRequest::READ_WRITE_OVERWRITE_OPEN_MODE) 134 | ? "wb+" 135 | : "rb"; // default to read-only 136 | 137 | if (FILE *fp = std::fopen(r->openFile.path->data, fopenMode)) { 138 | fileRecord->fp = fp; 139 | fileRecord->dependentClientCount = 1; 140 | r->openFile.fileHandle = fileRecord; 141 | r->resultStatus = NOERROR; 142 | } else { 143 | delete fileRecord; 144 | r->openFile.fileHandle = 0; 145 | r->resultStatus = (errno==NOERROR) ? EIO : errno; 146 | } 147 | } else { 148 | r->openFile.fileHandle = 0; 149 | r->resultStatus = ENOMEM; 150 | } 151 | 152 | completeRequestToClientResultQueue(r->openFile.resultQueue, r); 153 | } 154 | 155 | static void releaseFileRecordClientRef( FileRecord *fileRecord ) 156 | { 157 | if (--fileRecord->dependentClientCount == 0) { 158 | std::fclose(fileRecord->fp); 159 | delete fileRecord; 160 | } 161 | } 162 | 163 | static void handleCloseFileRequest( FileIoRequest *r ) 164 | { 165 | assert( r->requestType == FileIoRequest::CLOSE_FILE ); 166 | 167 | releaseFileRecordClientRef( static_cast(r->closeFile.fileHandle) ); 168 | freeFileIoRequest(r); 169 | } 170 | 171 | static void handleReadBlockRequest( FileIoRequest *r ) 172 | { 173 | assert( r->requestType == FileIoRequest::READ_BLOCK ); 174 | // Allocate the block, perform the fread, return the block to the client, 175 | // increment the file record dependent count. 176 | 177 | FileRecord *fileRecord = static_cast(r->readBlock.fileHandle); 178 | if (fileRecord) { 179 | DataBlock *dataBlock = allocDataBlock(); 180 | if (dataBlock) { 181 | 182 | // FIXME: we're only supporting 32 bit file positions here 183 | if (std::fseek(fileRecord->fp, r->readBlock.filePosition, SEEK_SET) != 0) { 184 | // seek failed 185 | r->resultStatus = (errno==NOERROR) ? EIO : errno; 186 | r->readBlock.dataBlock = 0; 187 | r->readBlock.isAtEof = false; 188 | freeDataBlock(dataBlock); 189 | dataBlock = 0; 190 | }else{ 191 | 192 | dataBlock->validCountBytes = std::fread(dataBlock->data, 1, dataBlock->capacityBytes, fileRecord->fp); 193 | 194 | if (dataBlock->validCountBytes==dataBlock->capacityBytes) { 195 | // normal case: read a whole block 196 | r->resultStatus = NOERROR; 197 | r->readBlock.dataBlock = dataBlock; // return the block 198 | r->readBlock.isAtEof = false; 199 | } else { 200 | // only return a partial block *if* we're at EOF, otherwise free the block and return an error. 201 | 202 | if (feof(fileRecord->fp) != 0) { 203 | // Note: this may return a block with zero valid bytes. Could maybe optimise this away. 204 | r->resultStatus = NOERROR; 205 | r->readBlock.dataBlock = dataBlock; // return the block 206 | r->readBlock.isAtEof = true; 207 | } else { 208 | r->resultStatus = (errno==NOERROR) ? EIO : errno; 209 | r->readBlock.dataBlock = 0; 210 | r->readBlock.isAtEof = false; 211 | freeDataBlock(dataBlock); 212 | dataBlock = 0; 213 | } 214 | } 215 | } 216 | }else{ 217 | r->resultStatus = ENOMEM; 218 | } 219 | }else{ 220 | r->resultStatus = EBADF; 221 | } 222 | 223 | if (r->readBlock.dataBlock) 224 | ++fileRecord->dependentClientCount; 225 | 226 | completeRequestToClientResultQueue(r->readBlock.resultQueue, r); 227 | } 228 | 229 | static void handleReleaseReadBlockRequest( FileIoRequest *r ) 230 | { 231 | assert( r->requestType == FileIoRequest::RELEASE_READ_BLOCK ); 232 | // Free the data block, decrement file record dependent client count 233 | 234 | assert( r->releaseReadBlock.dataBlock != 0 ); 235 | freeDataBlock(r->releaseReadBlock.dataBlock); 236 | releaseFileRecordClientRef( static_cast(r->releaseReadBlock.fileHandle) ); 237 | freeFileIoRequest(r); 238 | } 239 | 240 | static void handleAllocateWriteBlockRequest( FileIoRequest *r ) 241 | { 242 | assert( r->requestType == FileIoRequest::ALLOCATE_WRITE_BLOCK ); 243 | // Allocate the block, read existing data (if any), return the block to the client, 244 | // increment the file record dependent count 245 | 246 | FileRecord *fileRecord = static_cast(r->allocateWriteBlock.fileHandle); 247 | if (fileRecord) { 248 | DataBlock *dataBlock = allocDataBlock(); 249 | if (dataBlock) { 250 | 251 | // FIXME: we're only supporting 32 bit file positions here 252 | if (std::fseek(fileRecord->fp, r->allocateWriteBlock.filePosition, SEEK_SET) != 0) { 253 | // seek failed 254 | r->resultStatus = (errno==NOERROR) ? EIO : errno; 255 | r->allocateWriteBlock.dataBlock = 0; 256 | freeDataBlock(dataBlock); 257 | dataBlock = 0; 258 | }else{ 259 | 260 | dataBlock->validCountBytes = std::fread(dataBlock->data, 1, dataBlock->capacityBytes, fileRecord->fp); 261 | 262 | // irrespective of how many bytes are read, return the block to the client 263 | r->resultStatus = NOERROR; 264 | r->allocateWriteBlock.dataBlock = dataBlock; // return the block 265 | } 266 | }else{ 267 | r->resultStatus = ENOMEM; 268 | } 269 | }else{ 270 | r->resultStatus = EBADF; 271 | } 272 | 273 | if (r->allocateWriteBlock.dataBlock) 274 | ++fileRecord->dependentClientCount; 275 | 276 | completeRequestToClientResultQueue(r->allocateWriteBlock.resultQueue, r); 277 | } 278 | 279 | static void handleCommitModifiedWriteBlockRequest( FileIoRequest *r ) 280 | { 281 | assert( r->requestType == FileIoRequest::COMMIT_MODIFIED_WRITE_BLOCK ); 282 | // Write valid data to the file, free the data block, decrement file record dependent client count 283 | 284 | FileRecord *fileRecord = static_cast(r->commitModifiedWriteBlock.fileHandle); 285 | if (fileRecord) { 286 | // FIXME: we're only supporting 32 bit file positions here 287 | if (std::fseek(fileRecord->fp, r->commitModifiedWriteBlock.filePosition, SEEK_SET)==0) { 288 | DataBlock *dataBlock = r->commitModifiedWriteBlock.dataBlock; 289 | std::fwrite(dataBlock->data, 1, dataBlock->validCountBytes, fileRecord->fp); // silently ignore errors 290 | }else{ 291 | // couldn't seek to position, silently fail 292 | } 293 | } 294 | 295 | freeDataBlock(r->commitModifiedWriteBlock.dataBlock); 296 | releaseFileRecordClientRef( static_cast(r->commitModifiedWriteBlock.fileHandle) ); 297 | freeFileIoRequest(r); 298 | } 299 | 300 | static void handleReleaseUnmodifiedWriteBlockRequest( FileIoRequest *r ) 301 | { 302 | assert( r->requestType == FileIoRequest::RELEASE_UNMODIFIED_WRITE_BLOCK ); 303 | // Free the data block, decrement file record dependent client count 304 | 305 | freeDataBlock(r->releaseUnmodifiedWriteBlock.dataBlock); 306 | releaseFileRecordClientRef( static_cast(r->releaseUnmodifiedWriteBlock.fileHandle) ); 307 | freeFileIoRequest(r); 308 | } 309 | 310 | static void cleanupOneRequestResult( FileIoRequest *r ) 311 | { 312 | switch (r->requestType) // we only need to handle requests that return results here 313 | { 314 | case FileIoRequest::OPEN_FILE: 315 | { 316 | // in any case, release the path 317 | r->openFile.path->release(); 318 | r->openFile.path = 0; 319 | 320 | if (r->openFile.fileHandle != 0) { // the open was successful, close the handle 321 | // convert the open file request into a close request 322 | void *fileHandle = r->openFile.fileHandle; 323 | 324 | r->requestType = FileIoRequest::CLOSE_FILE; 325 | r->closeFile.fileHandle = fileHandle; 326 | handleCloseFileRequest(r); 327 | } else { 328 | freeFileIoRequest(r); 329 | } 330 | } 331 | break; 332 | 333 | case FileIoRequest::READ_BLOCK: 334 | { 335 | if (r->readBlock.dataBlock) { // the read was successful, release the block 336 | // convert the read block request into a release read block request 337 | void *fileHandle = r->readBlock.fileHandle; 338 | DataBlock *dataBlock = r->readBlock.dataBlock; 339 | 340 | r->requestType = FileIoRequest::RELEASE_READ_BLOCK; 341 | r->releaseReadBlock.fileHandle = fileHandle; 342 | r->releaseReadBlock.dataBlock = dataBlock; 343 | handleReleaseReadBlockRequest(r); 344 | } else { 345 | freeFileIoRequest(r); 346 | } 347 | } 348 | break; 349 | 350 | case FileIoRequest::ALLOCATE_WRITE_BLOCK: 351 | { 352 | if (r->allocateWriteBlock.dataBlock) { // the allocation was successful, release the block 353 | // convert the allocate write block request into a release write block request 354 | void *fileHandle = r->allocateWriteBlock.fileHandle; 355 | DataBlock *dataBlock = r->allocateWriteBlock.dataBlock; 356 | 357 | r->requestType = FileIoRequest::RELEASE_UNMODIFIED_WRITE_BLOCK; 358 | r->releaseUnmodifiedWriteBlock.fileHandle = fileHandle; 359 | r->releaseUnmodifiedWriteBlock.dataBlock = dataBlock; 360 | handleReleaseUnmodifiedWriteBlockRequest(r); 361 | } else { 362 | freeFileIoRequest(r); 363 | } 364 | } 365 | break; 366 | 367 | default: 368 | assert(false); // only requests that have results should be encountered 369 | } 370 | } 371 | 372 | static void handleCleanupResultQueueRequest( FileIoRequest *clientResultQueueContainer ) 373 | { 374 | // Cleanup any results that are in the queue, either free the queue now, or mark it for cleanup later. 375 | 376 | assert( clientResultQueueContainer->requestType == FileIoRequest::CLEANUP_RESULT_QUEUE ); 377 | 378 | if (clientResultQueueContainer->resultQueue.expectedResultCount() > 0) 379 | { 380 | while (FileIoRequest *r = clientResultQueueContainer->resultQueue.pop()) 381 | { 382 | cleanupOneRequestResult(r); 383 | } 384 | 385 | if (clientResultQueueContainer->resultQueue.expectedResultCount() == 0) { 386 | freeFileIoRequest(clientResultQueueContainer); 387 | } else { 388 | // mark the queue for cleanup. 389 | // cleanup is resumed by completeRequestToClientResultQueue() the next time that a request completes 390 | clientResultQueueContainer->requestType = FileIoRequest::RESULT_QUEUE_IS_AWAITING_CLEANUP_; 391 | } 392 | } else { 393 | freeFileIoRequest(clientResultQueueContainer); 394 | } 395 | } 396 | 397 | 398 | /////////////////////////////////////////////////////////////////////////////// 399 | // Server thread setup and teardown 400 | 401 | mint_atomic32_t shutdownFlag_; 402 | #ifdef WIN32 403 | HANDLE serverMailboxEvent_; 404 | HANDLE serverThreadHandle_; 405 | #else 406 | // google "mach semaphores amit singh" http://books.google.com.au/books?id=K8vUkpOXhN4C&pg=PA1219 407 | // and "OS X Kernel Programming Guide semaphores" https://developer.apple.com/library/mac/documentation/Darwin/Conceptual/KernelProgramming/synchronization/synchronization.html 408 | semaphore_t serverMailboxSemaphore_; 409 | pthread_t serverThread_; 410 | #endif 411 | 412 | QwMpscFifoQueue serverMailboxQueue_; 413 | 414 | static void handleAllPendingRequests() 415 | { 416 | while (FileIoRequest *r = serverMailboxQueue_.pop()) { 417 | switch (r->requestType) { 418 | case FileIoRequest::OPEN_FILE: 419 | handleOpenFileRequest(r); 420 | break; 421 | case FileIoRequest::CLOSE_FILE: 422 | handleCloseFileRequest(r); 423 | break; 424 | case FileIoRequest::READ_BLOCK: 425 | handleReadBlockRequest(r); 426 | break; 427 | case FileIoRequest::RELEASE_READ_BLOCK: 428 | handleReleaseReadBlockRequest(r); 429 | break; 430 | case FileIoRequest::ALLOCATE_WRITE_BLOCK: 431 | handleAllocateWriteBlockRequest(r); 432 | break; 433 | case FileIoRequest::COMMIT_MODIFIED_WRITE_BLOCK: 434 | handleCommitModifiedWriteBlockRequest(r); 435 | break; 436 | case FileIoRequest::RELEASE_UNMODIFIED_WRITE_BLOCK: 437 | handleReleaseUnmodifiedWriteBlockRequest(r); 438 | break; 439 | case FileIoRequest::CLEANUP_RESULT_QUEUE: 440 | handleCleanupResultQueueRequest(r); 441 | break; 442 | } 443 | } 444 | } 445 | 446 | 447 | #ifdef WIN32 448 | static unsigned int __stdcall serverThreadProc( void * ) 449 | { 450 | while (mint_load_32_relaxed(&shutdownFlag_) == 0) { 451 | WaitForSingleObject(serverMailboxEvent_, 1000); // note: only wait when the incoming queue is empty 452 | handleAllPendingRequests(); 453 | } 454 | 455 | return 0; 456 | } 457 | #else 458 | static void* serverThreadProc( void * ) 459 | { 460 | while (mint_load_32_relaxed(&shutdownFlag_) == 0) { 461 | semaphore_wait(serverMailboxSemaphore_); // note: only wait when the incoming queue is empty 462 | handleAllPendingRequests(); 463 | } 464 | 465 | return 0; 466 | } 467 | #endif 468 | 469 | 470 | void startFileIoServer( std::size_t fileIoRequestCount ) 471 | { 472 | globalRequestPool_ = new QwNodePool( fileIoRequestCount ); 473 | 474 | shutdownFlag_._nonatomic = 0; 475 | 476 | #ifdef WIN32 477 | serverMailboxEvent_ = CreateEvent( NULL, /*bManualReset=*/FALSE, /*bInitialState=*/FALSE, NULL ); // auto-reset event 478 | 479 | unsigned threadId; 480 | serverThreadHandle_ = (HANDLE)_beginthreadex( NULL, 0, serverThreadProc, NULL, 0, &threadId ); 481 | SetThreadPriority(serverThreadHandle_, THREAD_PRIORITY_TIME_CRITICAL); 482 | #else 483 | semaphore_create(mach_task_self(), &serverMailboxSemaphore_, SYNC_POLICY_FIFO, 0); 484 | 485 | pthread_attr_t threadAttrs; 486 | pthread_attr_init(&threadAttrs); 487 | 488 | pthread_create(&serverThread_, &threadAttrs, serverThreadProc, 0); 489 | #endif 490 | } 491 | 492 | 493 | void shutDownFileIoServer() 494 | { 495 | mint_store_32_relaxed(&shutdownFlag_, 1); 496 | 497 | #ifdef WIN32 498 | SetEvent(serverMailboxEvent_); 499 | 500 | WaitForSingleObject( serverThreadHandle_, 2000 ); 501 | CloseHandle( serverThreadHandle_ ); 502 | 503 | CloseHandle( serverMailboxEvent_ ); 504 | #else 505 | semaphore_signal(serverMailboxSemaphore_); 506 | 507 | pthread_join(serverThread_, 0); 508 | semaphore_destroy(mach_task_self(), serverMailboxSemaphore_); 509 | #endif 510 | 511 | delete globalRequestPool_; 512 | } 513 | 514 | 515 | void sendFileIoRequestToServer( FileIoRequest *r ) 516 | { 517 | bool wasEmpty=false; 518 | serverMailboxQueue_.push(r, wasEmpty); 519 | if (wasEmpty) { 520 | #ifdef WIN32 521 | SetEvent(serverMailboxEvent_); 522 | #else 523 | semaphore_signal(serverMailboxSemaphore_); 524 | #endif 525 | } 526 | } 527 | 528 | 529 | void sendFileIoRequestsToServer( FileIoRequest *front, FileIoRequest *back ) 530 | { 531 | bool wasEmpty=false; 532 | serverMailboxQueue_.push_multiple(front, back, wasEmpty); 533 | if (wasEmpty) { 534 | #ifdef WIN32 535 | SetEvent(serverMailboxEvent_); 536 | #else 537 | semaphore_signal(serverMailboxSemaphore_); 538 | #endif 539 | } 540 | } 541 | 542 | 543 | 544 | /* 545 | TODO: 546 | 547 | o- ensure server mailbox is cache-line aligned and that the server-local queue is separated from the global lifo 548 | 549 | 550 | x- example read stream routines (all asynchronous O(1) or near to) 551 | x- allocate a stream 552 | x- read data from the stream 553 | x- close the stream 554 | 555 | enum StreamState { STREAM_STATE_BUFFERING, STREAM_STATE_STREAMING }; 556 | 557 | typedef void ReadStream; 558 | ReadStream* ReadStream_open( const char *filePath ); 559 | void ReadStream_close( ReadStream *s ); 560 | StreamState ReadStream_getState( ReadStream *s ); 561 | size_t ReadStream_read( ReadStream *s, void *dest, size_t countBytes ); 562 | 563 | x- optional, later: support seeking 564 | 565 | x- example write stream routines (all asynchronous O(1) or near to) 566 | x- allocate a stream 567 | x- write data to the stream 568 | x- close the stream 569 | 570 | 571 | x- write an example program that can record and play. 572 | press r to start recording, s to stop recording. p to play the recording. 573 | 574 | 575 | 576 | // TODO: handle requests at two priority levels: 0 and 1 (0 is higher?) 577 | // lower priority requests go onto a secondary FIFO and only get processed after 578 | // the higher priority requests. 579 | // always process COMMIT_MODIFIED_WRITE_BLOCK at highest priority to avoid issues 580 | // with file length extending writes 581 | 582 | */ -------------------------------------------------------------------------------- /src/FileIoStreams.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Real Time File Streaming copyright (c) 2014 Ross Bencina 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | #include "FileIoStreams.h" 23 | 24 | #undef min 25 | #undef max 26 | #include 27 | #include 28 | #include 29 | 30 | #include "QwSpscUnorderedResultQueue.h" 31 | #include "QwSList.h" 32 | #include "QwSTailList.h" 33 | #include "FileIoServer.h" 34 | #include "SharedBuffer.h" 35 | #include "DataBlock.h" 36 | 37 | #ifndef NOERROR 38 | #define NOERROR (0) 39 | #endif 40 | 41 | //#define IO_USE_CONSTANT_TIME_RESULT_POLLING 42 | 43 | 44 | struct BlockRequestBehavior { // behavior with implementation common to read and write requests 45 | 46 | // L-value aliases. Map/alias request fields to our use of them. 47 | 48 | static FileIoRequest*& next_(FileIoRequest *r) { return r->links_[FileIoRequest::CLIENT_NEXT_LINK_INDEX]; } 49 | static FileIoRequest*& transitNext_(FileIoRequest *r) { return r->links_[FileIoRequest::TRANSIT_NEXT_LINK_INDEX]; } 50 | static int& state_(FileIoRequest *r) { return r->requestType; } 51 | static size_t& bytesCopied_(FileIoRequest *r) { return r->clientInt; } 52 | 53 | // predicates 54 | 55 | static bool isDiscarded(FileIoRequest *r) { return bytesCopied_(r)==-1; } 56 | static void setDiscarded(FileIoRequest *r) { bytesCopied_(r)=-1; } 57 | }; 58 | 59 | 60 | struct ReadBlockRequestBehavior : public BlockRequestBehavior { 61 | 62 | // when not in-flight, the request type field represents the state of the block request. 63 | // the acquire request type is mapped to the PENDING state. 64 | enum BlockState { 65 | BLOCK_STATE_PENDING = FileIoRequest::READ_BLOCK, 66 | BLOCK_STATE_READY = FileIoRequest::CLIENT_USE_BASE_, 67 | BLOCK_STATE_READY_MODIFIED, // not used for read streams 68 | BLOCK_STATE_ERROR 69 | }; 70 | 71 | // fields and predicates 72 | 73 | static DataBlock *dataBlock(FileIoRequest *r) { return r->readBlock.dataBlock; } 74 | static size_t filePosition(FileIoRequest *r) { return r->readBlock.filePosition; } 75 | 76 | static bool hasDataBlock(FileIoRequest *r) { return (dataBlock(r) != 0); } 77 | 78 | static bool isReady(FileIoRequest *r) { return (state_(r) == BLOCK_STATE_READY); } 79 | 80 | // request initialization and transformation 81 | 82 | // In abstract terms, data blocks are /acquired/ from the server and /released/ back to the server. 83 | // 84 | // For read-only streams READ_BLOCK is acquire and RELEASE_READ_BLOCK is release. 85 | // 86 | // For write-only streams ALLOCATE_WRITE_BLOCK is acquire and 87 | // (RELEASE_UNMODIFIED_WRITE_BLOCK or COMMIT_MODIFIED_WRITE_BLOCK) is release. 88 | 89 | static void initAcquire( FileIoRequest *blockReq, void *fileHandle, size_t pos, FileIoRequest *resultQueueReq ) 90 | { 91 | next_(blockReq) = 0; 92 | blockReq->resultStatus = 0; 93 | bytesCopied_(blockReq) = 0; 94 | blockReq->requestType = FileIoRequest::READ_BLOCK; 95 | 96 | blockReq->readBlock.fileHandle = fileHandle; 97 | blockReq->readBlock.filePosition = pos; 98 | blockReq->readBlock.dataBlock = 0; 99 | blockReq->readBlock.isAtEof = false; 100 | blockReq->readBlock.resultQueue = resultQueueReq; 101 | } 102 | 103 | static void transformToReleaseUnmodified( FileIoRequest *blockReq ) 104 | { 105 | void *fileHandle = blockReq->readBlock.fileHandle; 106 | DataBlock *dataBlock = blockReq->readBlock.dataBlock; 107 | 108 | blockReq->requestType = FileIoRequest::RELEASE_READ_BLOCK; 109 | blockReq->releaseReadBlock.fileHandle = fileHandle; 110 | blockReq->releaseReadBlock.dataBlock = dataBlock; 111 | } 112 | 113 | static void transformToCommitModified( FileIoRequest *blockReq ) 114 | { 115 | assert(false); // read blocks can't be committed, and won't be. because we never mark blocks as modified 116 | } 117 | 118 | // copyBlockData: Copy data into or out of the block. (out-of: read, in-to: write) 119 | 120 | typedef void* user_items_ptr_t; // this is const for write streams 121 | 122 | enum CopyStatus { CAN_CONTINUE, AT_BLOCK_END, AT_FINAL_BLOCK_END }; 123 | /* 124 | NOTE if we wanted to support items that span multiple blocks we could 125 | introduce an extra status: NEED_NEXT_BLOCK that we would return if the 126 | next block is pending but we needed data from it to copy an item. 127 | NEED_NEXT_BLOCK would cause the wrapper to go into the buffering state. 128 | */ 129 | 130 | static CopyStatus copyBlockData( FileIoRequest *blockReq, user_items_ptr_t userItemsPtr, 131 | size_t maxItemsToCopy, size_t itemSizeBytes, size_t *itemsCopiedResult ) 132 | { 133 | size_t blockBytesCopiedSoFar = bytesCopied_(blockReq); 134 | size_t bytesRemainingInBlock = dataBlock(blockReq)->validCountBytes - blockBytesCopiedSoFar; 135 | size_t wholeItemsRemainingInBlock = bytesRemainingInBlock / itemSizeBytes; 136 | 137 | // Assume that itemSize divides block size, otherwise we need to deal with items overlapping blocks 138 | // and that wouldn't be time-efficient (see NOTE earlier for possible implementation). 139 | assert( wholeItemsRemainingInBlock*itemSizeBytes == bytesRemainingInBlock ); 140 | 141 | size_t itemsToCopy = std::min(wholeItemsRemainingInBlock, maxItemsToCopy); 142 | 143 | size_t bytesToCopy = itemsToCopy*itemSizeBytes; 144 | std::memcpy(userItemsPtr, static_cast(dataBlock(blockReq)->data)+blockBytesCopiedSoFar, bytesToCopy); 145 | bytesCopied_(blockReq) += bytesToCopy; 146 | 147 | *itemsCopiedResult = itemsToCopy; 148 | 149 | if (itemsToCopy==wholeItemsRemainingInBlock) { 150 | if (blockReq->readBlock.isAtEof) 151 | return AT_FINAL_BLOCK_END; 152 | else 153 | return AT_BLOCK_END; 154 | } else { 155 | return CAN_CONTINUE; 156 | } 157 | } 158 | }; 159 | 160 | 161 | struct WriteBlockRequestBehavior : public BlockRequestBehavior { 162 | 163 | // when not in-flight, the request type field represents the state of the block request. 164 | // the acquire request type is mapped to the PENDING state. 165 | enum BlockState { 166 | BLOCK_STATE_PENDING = FileIoRequest::ALLOCATE_WRITE_BLOCK, 167 | BLOCK_STATE_READY = FileIoRequest::CLIENT_USE_BASE_, 168 | BLOCK_STATE_READY_MODIFIED, 169 | BLOCK_STATE_ERROR 170 | }; 171 | 172 | // fields and predicates 173 | 174 | static DataBlock *dataBlock(FileIoRequest *r) { return r->allocateWriteBlock.dataBlock; } 175 | static size_t filePosition(FileIoRequest *r) { return r->allocateWriteBlock.filePosition; } 176 | 177 | static bool hasDataBlock(FileIoRequest *r) { return (dataBlock(r) != 0); } 178 | 179 | static bool isReady(FileIoRequest *r) { return ((state_(r) == BLOCK_STATE_READY) || (state_(r) == BLOCK_STATE_READY_MODIFIED)); } 180 | 181 | // request initialization and transformation 182 | 183 | static void initAcquire( FileIoRequest *blockReq, void *fileHandle, size_t pos, FileIoRequest *resultQueueReq ) 184 | { 185 | next_(blockReq) = 0; 186 | blockReq->resultStatus = 0; 187 | bytesCopied_(blockReq) = 0; 188 | 189 | blockReq->requestType = FileIoRequest::ALLOCATE_WRITE_BLOCK; 190 | blockReq->allocateWriteBlock.fileHandle = fileHandle; 191 | blockReq->allocateWriteBlock.filePosition = pos; 192 | blockReq->allocateWriteBlock.dataBlock = 0; 193 | blockReq->allocateWriteBlock.resultQueue = resultQueueReq; 194 | } 195 | 196 | static void transformToReleaseUnmodified( FileIoRequest *blockReq ) 197 | { 198 | void *fileHandle = blockReq->allocateWriteBlock.fileHandle; 199 | DataBlock *dataBlock = blockReq->allocateWriteBlock.dataBlock; 200 | 201 | blockReq->requestType = FileIoRequest::RELEASE_UNMODIFIED_WRITE_BLOCK; 202 | blockReq->releaseUnmodifiedWriteBlock.fileHandle = fileHandle; 203 | blockReq->releaseUnmodifiedWriteBlock.dataBlock = dataBlock; 204 | } 205 | 206 | static void transformToCommitModified( FileIoRequest *blockReq ) 207 | { 208 | void *fileHandle = blockReq->allocateWriteBlock.fileHandle; 209 | std::size_t filePosition = blockReq->allocateWriteBlock.filePosition; 210 | DataBlock *dataBlock = blockReq->allocateWriteBlock.dataBlock; 211 | 212 | blockReq->requestType = FileIoRequest::COMMIT_MODIFIED_WRITE_BLOCK; 213 | blockReq->commitModifiedWriteBlock.fileHandle = fileHandle; 214 | blockReq->commitModifiedWriteBlock.filePosition = filePosition; 215 | blockReq->commitModifiedWriteBlock.dataBlock = dataBlock; 216 | } 217 | 218 | // copyBlockData: Copy data into or out of the block. (out-of: read, in-to: write) 219 | 220 | typedef const void* user_items_ptr_t; 221 | 222 | enum CopyStatus { CAN_CONTINUE, AT_BLOCK_END, AT_FINAL_BLOCK_END }; 223 | 224 | static CopyStatus copyBlockData( FileIoRequest *blockReq, user_items_ptr_t userItemsPtr, 225 | size_t maxItemsToCopy, size_t itemSizeBytes, size_t *itemsCopiedResult ) 226 | { 227 | size_t blockBytesCopiedSoFar = bytesCopied_(blockReq); 228 | size_t bytesRemainingInBlock = IO_DATA_BLOCK_DATA_CAPACITY_BYTES - blockBytesCopiedSoFar; 229 | size_t wholeItemsRemainingInBlock = bytesRemainingInBlock / itemSizeBytes; 230 | 231 | // Assume that itemSize divides block size, otherwise we need to deal with items overlapping blocks 232 | // and that wouldn't be time-efficient (see NOTE earlier for possible implementation). 233 | assert( wholeItemsRemainingInBlock*itemSizeBytes == bytesRemainingInBlock ); 234 | 235 | if (dataBlock(blockReq)->validCountBytes < blockBytesCopiedSoFar) { 236 | // if bytesCopied_ is manipulated to start writing somewhere other than the start of the block 237 | // we may have a situation where we need to zero the first part of the block. 238 | 239 | std::memset(static_cast(dataBlock(blockReq)->data)+dataBlock(blockReq)->validCountBytes, 240 | 0, blockBytesCopiedSoFar-dataBlock(blockReq)->validCountBytes); 241 | dataBlock(blockReq)->validCountBytes = blockBytesCopiedSoFar; 242 | } 243 | 244 | size_t itemsToCopy = std::min(wholeItemsRemainingInBlock, maxItemsToCopy); 245 | 246 | size_t bytesToCopy = itemsToCopy*itemSizeBytes; 247 | std::memcpy(static_cast(dataBlock(blockReq)->data)+blockBytesCopiedSoFar, userItemsPtr, bytesToCopy); 248 | bytesCopied_(blockReq) += bytesToCopy; 249 | 250 | dataBlock(blockReq)->validCountBytes = bytesCopied_(blockReq); 251 | state_(blockReq) = BLOCK_STATE_READY_MODIFIED; 252 | 253 | *itemsCopiedResult = itemsToCopy; 254 | 255 | if (itemsToCopy==wholeItemsRemainingInBlock) { 256 | return AT_BLOCK_END; 257 | } else { 258 | return CAN_CONTINUE; 259 | } 260 | } 261 | }; 262 | 263 | 264 | template< typename BlockReq, typename StreamType > 265 | class FileIoStreamWrapper { // Object-oriented wrapper for a read and write streams 266 | 267 | typedef StreamType STREAMTYPE; // READSTREAM or WRITESTREAM 268 | 269 | FileIoRequest *resultQueueReq_; // The data structure is represented by a linked structure of FileIoRequest objects 270 | 271 | /* 272 | The stream data structure is composed of linked request nodes. 273 | OpenFileReq is linked by the result queue's transit link. This works because 274 | the transit link is not used unless the RQ is posted to the server for cleanup. 275 | 276 | READSTREAM* (or WRITESTREAM*) 277 | | 278 | | (resultQueueReq_) 279 | V 280 | [ result queue ] -> [ OPEN_FILE ] ------------------ 281 | | (openFileReq) | 282 | (head) | (tail) | 283 | V V 284 | [ READ_BLOCK ] -> [ READ_BLOCK ] -> ... -> [ READ_BLOCK ] -> NULL } (prefetchQueue) 285 | 286 | 287 | "[ ... ]" indicates a FileIoRequest structure. 288 | 289 | For a write stream, the prefetch queue contains ALLOCATE_WRITE_BLOCK requests. 290 | */ 291 | 292 | // Stream field lvalue aliases. Map/alias request fields to fields of our pseudo-class. 293 | 294 | FileIoRequest*& openFileReqLink_() { return resultQueueReq_->links_[FileIoRequest::TRANSIT_NEXT_LINK_INDEX]; } 295 | FileIoRequest* openFileReq() { return openFileReqLink_(); } 296 | int& state_() { return resultQueueReq_->requestType; } 297 | int& error_() { return resultQueueReq_->resultStatus; } 298 | FileIoRequest*& prefetchQueueHead_() { return resultQueueReq_->links_[FileIoRequest::CLIENT_NEXT_LINK_INDEX]; } 299 | FileIoRequest*& prefetchQueueTail_() { return openFileReq()->links_[FileIoRequest::CLIENT_NEXT_LINK_INDEX]; } 300 | size_t& waitingForBlocksCount_() { return resultQueueReq_->clientInt; } 301 | FileIoRequest::result_queue_t& resultQueue() { return resultQueueReq_->resultQueue; } 302 | 303 | FileIoStreamWrapper( FileIoRequest *resultQueueReq ) 304 | : resultQueueReq_( resultQueueReq ) {} 305 | 306 | FileIoRequest* prefetchQueue_front() 307 | { 308 | return prefetchQueueHead_(); 309 | } 310 | 311 | void prefetchQueue_pop_front() 312 | { 313 | FileIoRequest *x = prefetchQueueHead_(); 314 | prefetchQueueHead_() = BlockReq::next_(x); 315 | BlockReq::next_(x) = 0; 316 | } 317 | 318 | void prefetchQueue_push_back( FileIoRequest *blockReq ) 319 | { 320 | assert( prefetchQueueTail_() != 0 ); // doesn't deal with an empty queue, doesn't need to 321 | BlockReq::next_(prefetchQueueTail_()) = blockReq; 322 | prefetchQueueTail_() = blockReq; 323 | } 324 | 325 | void sendAcquireBlockRequestToServer( FileIoRequest *blockReq ) 326 | { 327 | ::sendFileIoRequestToServer(blockReq); 328 | resultQueue().incrementExpectedResultCount(); 329 | ++waitingForBlocksCount_(); 330 | } 331 | 332 | void sendAcquireBlockRequestsToServer( FileIoRequest *front, FileIoRequest *back, size_t count ) 333 | { 334 | ::sendFileIoRequestsToServer(front, back); 335 | resultQueue().incrementExpectedResultCount(count); 336 | waitingForBlocksCount_() += count; 337 | } 338 | 339 | // Init, link and send sequential block request 340 | void initAndLinkSequentialAcquireBlockRequest( FileIoRequest *blockReq ) 341 | { 342 | // Init, link, and send a sequential data block acquire request (READ_BLOCK or ALLOCATE_WRITE_BLOCK). 343 | // Init the block request so that it's file position directly follows the tail block in the prefetch queue; 344 | // link the request onto the back of the prefetch queue; send the request to the server. 345 | 346 | // precondition: prefetch queue is non-empty 347 | assert( prefetchQueueHead_() !=0 && prefetchQueueTail_() !=0 ); 348 | 349 | BlockReq::initAcquire( blockReq, openFileReq()->openFile.fileHandle, 350 | BlockReq::filePosition(prefetchQueueTail_()) + IO_DATA_BLOCK_DATA_CAPACITY_BYTES, resultQueueReq_ ); 351 | 352 | prefetchQueue_push_back(blockReq); 353 | } 354 | 355 | template< typename ReturnToServerFunc > 356 | void flushBlock( FileIoRequest *blockReq, ReturnToServerFunc returnToServer ) 357 | { 358 | switch (BlockReq::state_(blockReq)) 359 | { 360 | case BlockReq::BLOCK_STATE_PENDING: 361 | // Forget the block, it will show up in the result queue later 362 | // and will be cleaned up from there. 363 | 364 | // Setting the "discarded" flag is used when the stream is still alive and we 365 | // need to remove a pending request from the prefetch queue. 366 | // See receiveOneBlock() for discarded block handling. 367 | BlockReq::setDiscarded(blockReq); 368 | --waitingForBlocksCount_(); 369 | break; 370 | 371 | case BlockReq::BLOCK_STATE_READY: 372 | assert( BlockReq::hasDataBlock(blockReq) ); 373 | BlockReq::transformToReleaseUnmodified(blockReq); 374 | returnToServer(blockReq); 375 | break; 376 | 377 | case BlockReq::BLOCK_STATE_READY_MODIFIED: 378 | assert( BlockReq::hasDataBlock(blockReq) ); 379 | BlockReq::transformToCommitModified(blockReq); 380 | returnToServer(blockReq); 381 | break; 382 | 383 | case BlockReq::BLOCK_STATE_ERROR: 384 | assert( !BlockReq::hasDataBlock(blockReq) ); 385 | freeFileIoRequest( blockReq ); 386 | break; 387 | } 388 | } 389 | 390 | void flushPrefetchQueue() 391 | { 392 | // Accumulate requests to return to server in a list and send them using a single operation. 393 | typedef QwSTailList transit_list_t; 394 | transit_list_t blockRequests; 395 | 396 | // For each block in the prefetch queue, pop the block from the 397 | // head of the queue and clean up the block. 398 | while (prefetchQueueHead_()) { 399 | FileIoRequest *blockReq = prefetchQueue_front(); 400 | prefetchQueue_pop_front(); 401 | flushBlock(blockReq, 402 | std::bind1st(std::mem_fun(&transit_list_t::push_front), &blockRequests)); 403 | } 404 | 405 | prefetchQueueTail_() = 0; 406 | assert( waitingForBlocksCount_() == 0 ); 407 | 408 | if (!blockRequests.empty()) 409 | ::sendFileIoRequestsToServer(blockRequests.front(), blockRequests.back()); 410 | } 411 | 412 | static size_t roundDownToBlockSizeAlignedPosition( size_t pos ) 413 | { 414 | size_t blockNumber = pos / IO_DATA_BLOCK_DATA_CAPACITY_BYTES; 415 | return blockNumber * IO_DATA_BLOCK_DATA_CAPACITY_BYTES; 416 | } 417 | 418 | // Should only be called after the stream has been opened and before it is closed. 419 | // returns true if a block was processed. 420 | bool receiveOneBlock() 421 | { 422 | if (FileIoRequest *r=resultQueue().pop()) { 423 | assert( BlockReq::state_(r) == BlockReq::BLOCK_STATE_PENDING ); 424 | 425 | if (BlockReq::isDiscarded(r)) { 426 | // the block was discarded. i.e. is no longer in the prefetch block queue 427 | 428 | if (r->resultStatus==NOERROR) { 429 | BlockReq::transformToReleaseUnmodified(r); 430 | ::sendFileIoRequestToServer(r); 431 | } else { 432 | assert( BlockReq::hasDataBlock(r) ); 433 | ::freeFileIoRequest(r); 434 | // (errors on discarded blocks don't affect the stream state) 435 | } 436 | // (discarded blocks do not count against waitingForBlocksCount_ 437 | 438 | } else { 439 | if (--waitingForBlocksCount_() == 0) 440 | state_() = STREAM_STATE_OPEN_STREAMING; 441 | 442 | if (r->resultStatus==NOERROR) { 443 | assert( BlockReq::hasDataBlock(r) ); 444 | BlockReq::state_(r) = BlockReq::BLOCK_STATE_READY; 445 | } else { 446 | // Mark the request as in ERROR. The stream state will switch to ERROR when the client tries to read/write the block. 447 | BlockReq::state_(r) = BlockReq::BLOCK_STATE_ERROR; 448 | } 449 | } 450 | 451 | return true; 452 | } 453 | 454 | return false; 455 | } 456 | 457 | bool advanceToNextBlock() 458 | { 459 | // issue next block request, link it on to the tail of the prefetch queue... 460 | 461 | FileIoRequest *newBlockReq = allocFileIoRequest(); 462 | if (!newBlockReq) { 463 | // Fail. couldn't allocate request 464 | state_() = STREAM_STATE_ERROR; 465 | return false; 466 | } 467 | initAndLinkSequentialAcquireBlockRequest(newBlockReq); 468 | sendAcquireBlockRequestToServer(newBlockReq); 469 | 470 | // unlink and flush the old block... 471 | 472 | // Notice that we link the new request on the back of the prefetch queue before unlinking 473 | // the old one off the front, so there is no chance of having to deal with the special case of 474 | // linking to an empty queue. 475 | 476 | FileIoRequest *oldBlockReq = prefetchQueue_front(); 477 | prefetchQueue_pop_front(); // advance head to next block 478 | flushBlock(oldBlockReq, ::sendFileIoRequestToServer); 479 | 480 | return true; 481 | } 482 | 483 | public: 484 | FileIoStreamWrapper( STREAMTYPE *fp ) 485 | : resultQueueReq_( static_cast(fp) ) {} 486 | 487 | static STREAMTYPE* open( SharedBuffer *path, FileIoRequest::OpenMode openMode ) 488 | { 489 | // Allocate two requests. Return 0 if allocation fails. 490 | 491 | FileIoRequest *resultQueueReq = allocFileIoRequest(); 492 | if (!resultQueueReq) 493 | return 0; 494 | 495 | FileIoRequest *openFileReq = allocFileIoRequest(); 496 | if (!openFileReq) { 497 | freeFileIoRequest(resultQueueReq); 498 | return 0; 499 | } 500 | 501 | // Initialise the stream data structure 502 | 503 | FileIoStreamWrapper stream(resultQueueReq); 504 | //std::memset( &resultQueueReq, 0, sizeof(FileIoRequest) ); 505 | stream.resultQueue().init(); 506 | 507 | stream.openFileReqLink_() = openFileReq; 508 | stream.state_() = STREAM_STATE_OPENING; 509 | stream.error_() = 0; 510 | stream.prefetchQueueHead_() = 0; 511 | stream.prefetchQueueTail_() = 0; 512 | stream.waitingForBlocksCount_() = 0; 513 | 514 | // Issue the OPEN_FILE request 515 | 516 | openFileReq->resultStatus = 0; 517 | openFileReq->requestType = FileIoRequest::OPEN_FILE; 518 | path->addRef(); 519 | openFileReq->openFile.path = path; 520 | openFileReq->openFile.openMode = openMode; 521 | openFileReq->openFile.fileHandle = IO_INVALID_FILE_HANDLE; 522 | openFileReq->openFile.resultQueue = resultQueueReq; 523 | 524 | ::sendFileIoRequestToServer(openFileReq); 525 | stream.resultQueue().incrementExpectedResultCount(); 526 | 527 | return static_cast(resultQueueReq); 528 | } 529 | 530 | void close() 531 | { 532 | // (Don't poll state, just dispose current state) 533 | 534 | if (state_()==STREAM_STATE_OPENING) { 535 | // Still waiting for OPEN_FILE to return. Send the result queue to the server for cleanup. 536 | openFileReqLink_() = 0; 537 | 538 | resultQueueReq_->requestType = FileIoRequest::CLEANUP_RESULT_QUEUE; 539 | ::sendFileIoRequestToServer(resultQueueReq_); 540 | resultQueueReq_ = 0; 541 | } else { 542 | // Stream is open. The prefetch queue may contain requests. 543 | 544 | // Dispose the prefetch queue, if it's populated 545 | 546 | flushPrefetchQueue(); 547 | 548 | // Clean up the open file request 549 | 550 | { 551 | FileIoRequest *openFileReq = openFileReqLink_(); 552 | openFileReqLink_() = 0; 553 | if (openFileReq->openFile.fileHandle != IO_INVALID_FILE_HANDLE) { 554 | // Transform openFileReq to CLOSE_FILE and send to server 555 | void *fileHandle = openFileReq->openFile.fileHandle; 556 | 557 | FileIoRequest *closeFileReq = openFileReq; 558 | closeFileReq->requestType = FileIoRequest::CLOSE_FILE; 559 | closeFileReq->closeFile.fileHandle = fileHandle; 560 | ::sendFileIoRequestToServer(closeFileReq); 561 | } else { 562 | freeFileIoRequest(openFileReq); 563 | } 564 | } 565 | 566 | // Clean up the result queue 567 | 568 | if (resultQueue().expectedResultCount() > 0) { 569 | // Send the result queue to the server for cleanup 570 | resultQueueReq_->requestType = FileIoRequest::CLEANUP_RESULT_QUEUE; 571 | ::sendFileIoRequestToServer(resultQueueReq_); 572 | resultQueueReq_ = 0; 573 | } else { 574 | freeFileIoRequest(resultQueueReq_); 575 | resultQueueReq_ = 0; 576 | } 577 | } 578 | } 579 | 580 | int seek( size_t pos ) 581 | { 582 | assert( pos >= 0 ); 583 | 584 | if (state_() == STREAM_STATE_OPENING || state_() == STREAM_STATE_ERROR) 585 | return -1; 586 | 587 | // Straight-forward implementation of seek: dump all blocks from the prefetch queue, 588 | // then request the needed blocks. 589 | // A more optimised version would retain any needed blocks from the current prefetch queue. 590 | 591 | flushPrefetchQueue(); 592 | 593 | // HACK: Hardcode the prefetch queue length. 594 | // The prefetch queue length should be computed from the stream data rate and the 595 | // desired prefetch buffering length (in seconds). 596 | 597 | const int prefetchQueueBlockCount = 20; 598 | 599 | // Request blocks on block-size-aligned boundaries 600 | size_t blockFilePositionBytes = roundDownToBlockSizeAlignedPosition(pos); 601 | 602 | // request the first block 603 | 604 | FileIoRequest *firstBlockReq = allocFileIoRequest(); 605 | if (!firstBlockReq) { 606 | state_() = STREAM_STATE_ERROR; 607 | return -1; 608 | } 609 | 610 | BlockReq::initAcquire( firstBlockReq, openFileReq()->openFile.fileHandle, blockFilePositionBytes, resultQueueReq_ ); 611 | 612 | BlockReq::bytesCopied_(firstBlockReq) = pos - blockFilePositionBytes; // compensate for block-size-aligned request 613 | 614 | prefetchQueueHead_() = firstBlockReq; 615 | prefetchQueueTail_() = firstBlockReq; 616 | 617 | // Optimisation: enqueue all block requests at once. First link them into a list, then enqueue them. 618 | // This minimises contention on the communication queue by only invoking a single push operation. 619 | 620 | // Construct a linked list of block requests to enqueue, firstBlockReq will be at the tail. 621 | // lastBlockReq -> ... -> firstBlockReq -> 0 622 | QwSList blockRequests; 623 | BlockReq::transitNext_(firstBlockReq) = 0; 624 | blockRequests.push_front(firstBlockReq); 625 | 626 | for (int i=1; i < prefetchQueueBlockCount; ++i) { 627 | FileIoRequest *blockReq = allocFileIoRequest(); 628 | if (!blockReq) { 629 | // Fail. couldn't allocate request. 630 | 631 | // Rollback: deallocate all allocated block requests 632 | while (!blockRequests.empty()) { 633 | FileIoRequest *r = blockRequests.front(); 634 | blockRequests.pop_front(); 635 | freeFileIoRequest(r); 636 | } 637 | 638 | state_() = STREAM_STATE_ERROR; 639 | return -1; 640 | } 641 | 642 | initAndLinkSequentialAcquireBlockRequest(blockReq); 643 | blockRequests.push_front(blockReq); 644 | } 645 | 646 | sendAcquireBlockRequestsToServer(blockRequests.front(), firstBlockReq, prefetchQueueBlockCount); 647 | 648 | state_() = STREAM_STATE_OPEN_BUFFERING; 649 | 650 | return 0; 651 | } 652 | 653 | typedef typename BlockReq::user_items_ptr_t user_items_ptr_t; 654 | 655 | size_t read_or_write( user_items_ptr_t userItemsPtr, size_t itemSizeBytes, size_t itemCount ) // for a read stream this is read(), for a write stream this is write() 656 | { 657 | // Always process at least one expected reply from the server per read/write call by calling pollState(). 658 | // If read_or_write() reads at most N bytes, and (IO_DATA_BLOCK_DATA_CAPACITY_BYTES/N) 659 | // is greater than the maximum result queue length, then this single poll is almost always 660 | // sufficient to retire all pending results before then are needed. The conditions where it 661 | // isn't sufficient require that the prefetch buffer must be slightly longer than if we 662 | // processed all pending replies as soon as they were available (consider the case where 663 | // replies arrive in reverse order just before their deadline). 664 | 665 | pollState(); // Updates state based on received replies. e.g. from BUFFERING to STREAMING 666 | 667 | switch (state_()) 668 | { 669 | case STREAM_STATE_OPENING: 670 | case STREAM_STATE_OPEN_IDLE: 671 | case STREAM_STATE_OPEN_EOF: 672 | case STREAM_STATE_ERROR: 673 | return 0; 674 | break; 675 | 676 | case STREAM_STATE_OPEN_BUFFERING: 677 | // In BUFFERING the stream can still read or write if there is 678 | // data available. Hence the fall-through below. If clients want to 679 | // pause while the stream is buffering they need to poll the state and 680 | // implement their own pause logic. 681 | { 682 | #if defined(IO_USE_CONSTANT_TIME_RESULT_POLLING) 683 | // fall through 684 | #else 685 | // The call to pollState() above only deals with at most one pending block. 686 | // To reduce the latency of transitioning from BUFFERING to STREAMING we can drain the result queue here. 687 | // This is O(N) in the number of expected results. 688 | 689 | while (receiveOneBlock()) 690 | /* loop until all replies have been processed */ ; 691 | 692 | if (state_() != STREAM_STATE_OPEN_STREAMING && state_() != STREAM_STATE_OPEN_BUFFERING) 693 | return 0; 694 | #endif 695 | } 696 | /* FALLS THROUGH */ 697 | 698 | case STREAM_STATE_OPEN_STREAMING: 699 | { 700 | int8_t *userBytesPtr = (int8_t*)userItemsPtr; 701 | const size_t maxItemsToCopy = itemCount; 702 | size_t itemsCopiedSoFar = 0; 703 | 704 | while (itemsCopiedSoFar < maxItemsToCopy) { 705 | FileIoRequest *frontBlockReq = prefetchQueue_front(); 706 | assert( frontBlockReq != 0 ); 707 | 708 | #if !defined(IO_USE_CONSTANT_TIME_RESULT_POLLING) 709 | // Last-ditch effort to determine whether the front block has been returned. 710 | // O(n) in the maximum number of expected replies. 711 | // Since we always poll at least one block per read/write operation (call to 712 | // pollState() above), the following loop is not strictly necessary. 713 | // It lessens the likelihood of a buffer underrun. 714 | 715 | // Process replies until the front block is not pending or there are no more replies 716 | while (BlockReq::state_(frontBlockReq) == BlockReq::BLOCK_STATE_PENDING) { 717 | if (!receiveOneBlock()) 718 | break; 719 | } 720 | #endif 721 | 722 | if (BlockReq::isReady(frontBlockReq)) { 723 | // copy data to/from userItemsPtr and the front block in the prefetch queue 724 | 725 | size_t itemsRemainingToCopy = maxItemsToCopy - itemsCopiedSoFar; 726 | 727 | size_t itemsCopied = 0; 728 | typename BlockReq::CopyStatus copyStatus = BlockReq::copyBlockData(frontBlockReq, userBytesPtr, itemsRemainingToCopy, itemSizeBytes, &itemsCopied); 729 | 730 | userBytesPtr += itemsCopied * itemSizeBytes; 731 | itemsCopiedSoFar += itemsCopied; 732 | 733 | switch (copyStatus) { 734 | case BlockReq::AT_BLOCK_END: 735 | if (!advanceToNextBlock()) 736 | return itemsCopiedSoFar; // advance failed 737 | 738 | #if 0 739 | // To reduce latency on streaming reads we could check for new results here. 740 | // This is especially useful if itemCount > items per block or if the server 741 | // can run faster than the stream. 742 | receiveOneBlock(); 743 | #endif 744 | break; 745 | case BlockReq::AT_FINAL_BLOCK_END: 746 | state_() = STREAM_STATE_OPEN_EOF; 747 | return itemsCopiedSoFar; 748 | break; 749 | case BlockReq::CAN_CONTINUE: 750 | /* NOTHING */ 751 | break; 752 | } 753 | } else if(BlockReq::state_(frontBlockReq) == BlockReq::BLOCK_STATE_PENDING) { 754 | state_() = STREAM_STATE_OPEN_BUFFERING; 755 | return itemsCopiedSoFar; 756 | } else { 757 | assert( BlockReq::state_(frontBlockReq) == BlockReq::BLOCK_STATE_ERROR ); 758 | state_() = STREAM_STATE_ERROR; 759 | return itemsCopiedSoFar; 760 | } 761 | } 762 | 763 | assert( itemsCopiedSoFar == maxItemsToCopy ); 764 | return maxItemsToCopy; 765 | } 766 | break; 767 | } 768 | 769 | return 0; 770 | } 771 | 772 | FileIoStreamState pollState() 773 | { 774 | if (resultQueue().expectedResultCount() > 0) { 775 | if (state_()==STREAM_STATE_OPENING) { 776 | if (FileIoRequest *r=resultQueue().pop()) { 777 | assert( r == openFileReq() ); // When opening, the only possible result is the open file request. 778 | 779 | r->openFile.path->release(); 780 | r->openFile.path = 0; 781 | 782 | if (r->resultStatus==NOERROR) { 783 | assert( r->openFile.fileHandle != 0 ); 784 | 785 | state_() = STREAM_STATE_OPEN_IDLE; 786 | // NOTE: In principle we could seek here. at the moment we require the client to poll for idle. 787 | } else { 788 | error_() = r->resultStatus; 789 | state_() = STREAM_STATE_ERROR; 790 | } 791 | 792 | // Leave openFileReq linked to the structure, even if there's an error. 793 | } 794 | } else { 795 | assert( state_()==STREAM_STATE_OPEN_IDLE 796 | || state_()==STREAM_STATE_OPEN_EOF 797 | || state_()==STREAM_STATE_OPEN_BUFFERING 798 | || state_()==STREAM_STATE_OPEN_STREAMING 799 | || state_()==STREAM_STATE_ERROR ); 800 | 801 | receiveOneBlock(); 802 | } 803 | } 804 | 805 | return (FileIoStreamState)state_(); 806 | } 807 | 808 | int getError() 809 | { 810 | return error_(); 811 | } 812 | }; 813 | 814 | 815 | // read stream 816 | 817 | typedef FileIoStreamWrapper FileIoReadStreamWrapper; 818 | 819 | READSTREAM *FileIoReadStream_open( SharedBuffer *path, FileIoRequest::OpenMode openMode ) 820 | { 821 | return FileIoReadStreamWrapper::open(path, openMode); 822 | } 823 | 824 | void FileIoReadStream_close( READSTREAM *fp ) 825 | { 826 | FileIoReadStreamWrapper(fp).close(); 827 | } 828 | 829 | int FileIoReadStream_seek( READSTREAM *fp, size_t pos ) 830 | { 831 | return FileIoReadStreamWrapper(fp).seek(pos); 832 | } 833 | 834 | size_t FileIoReadStream_read( void *dest, size_t itemSize, size_t itemCount, READSTREAM *fp ) 835 | { 836 | return FileIoReadStreamWrapper(fp).read_or_write(dest, itemSize, itemCount); 837 | } 838 | 839 | FileIoStreamState FileIoReadStream_pollState( READSTREAM *fp ) 840 | { 841 | return FileIoReadStreamWrapper(fp).pollState(); 842 | } 843 | 844 | int FileIoReadStream_getError( READSTREAM *fp ) 845 | { 846 | return FileIoReadStreamWrapper(fp).getError(); 847 | } 848 | 849 | 850 | // write stream 851 | 852 | typedef FileIoStreamWrapper FileIoWriteStreamWrapper; 853 | 854 | WRITESTREAM *FileIoWriteStream_open( SharedBuffer *path, FileIoRequest::OpenMode openMode ) 855 | { 856 | return FileIoWriteStreamWrapper::open(path, openMode); 857 | } 858 | 859 | void FileIoWriteStream_close( WRITESTREAM *fp ) 860 | { 861 | FileIoWriteStreamWrapper(fp).close(); 862 | } 863 | 864 | int FileIoWriteStream_seek( WRITESTREAM *fp, size_t pos ) 865 | { 866 | return FileIoWriteStreamWrapper(fp).seek(pos); 867 | } 868 | 869 | size_t FileIoWriteStream_write( const void *src, size_t itemSize, size_t itemCount, WRITESTREAM *fp ) 870 | { 871 | return FileIoWriteStreamWrapper(fp).read_or_write(src, itemSize, itemCount); 872 | } 873 | 874 | FileIoStreamState FileIoWriteStream_pollState( WRITESTREAM *fp ) 875 | { 876 | return FileIoWriteStreamWrapper(fp).pollState(); 877 | } 878 | 879 | int FileIoWriteStream_getError( WRITESTREAM *fp ) 880 | { 881 | return FileIoWriteStreamWrapper(fp).getError(); 882 | } 883 | --------------------------------------------------------------------------------