diff --git a/internal/client/files-uploading.go b/internal/client/files-uploading.go index 0b40433..9cf0256 100644 --- a/internal/client/files-uploading.go +++ b/internal/client/files-uploading.go @@ -132,14 +132,17 @@ func uploadFileByChunks(stream pb.CompilationService_UploadFileStreamClient, chu defer fd.Close() var n int + var sentChunks = 0 // used to correctly handle empty files (when Read returns EOF immediately) for { n, err = fd.Read(chunkBuf) - if err == io.EOF { - break - } - if err != nil { + if err != nil && err != io.EOF { return err } + if err == io.EOF && sentChunks != 0 { + break + } + sentChunks++ + err = stream.Send(&pb.UploadFileChunkRequest{ ClientID: clientID, SessionID: sessionID, diff --git a/internal/client/invocation.go b/internal/client/invocation.go index d702e20..6f24d79 100644 --- a/internal/client/invocation.go +++ b/internal/client/invocation.go @@ -34,9 +34,10 @@ type Invocation struct { cxxIDirs IncludeDirs // -I / -iquote / -isystem go here depsFlags DepCmdFlags // -MD -MF file and others, used for .d files generation (not passed to server) - doneState int32 // see Invocation.DoneRecvObj - wgUpload sync.WaitGroup - wgRecv sync.WaitGroup + waitUploads int32 // files still waiting for upload to finish; 0 releases wgUpload; see Invocation.DoneUploadFile + doneRecv int32 // 1 if o file received or failed receiving; 1 releases wgRecv; see Invocation.DoneRecvObj + wgUpload sync.WaitGroup + wgRecv sync.WaitGroup // when remote compilation starts, the server starts a server.Session (with the same sessionID) // after it finishes, we have these fields filled (and objOutFile saved) @@ -232,7 +233,7 @@ func (invocation *Invocation) CollectDependentIncludes(disableOwnIncludes bool) } func (invocation *Invocation) DoneRecvObj(err error) { - if atomic.SwapInt32(&invocation.doneState, 1) == 0 { + if atomic.SwapInt32(&invocation.doneRecv, 1) == 0 { if err != nil { invocation.err = err } @@ -244,11 +245,16 @@ func (invocation *Invocation) DoneUploadFile(err error) { if err != nil { invocation.err = err } + atomic.AddInt32(&invocation.waitUploads, -1) invocation.wgUpload.Done() // will end up after all required files uploaded/failed } func (invocation *Invocation) ForceInterrupt(err error) { - // hopefully, an invocation can't hang on uploading files, it could hang only on waiting for obj logClient.Error("force interrupt", "sessionID", invocation.sessionID, invocation.cppInFile, err) + // release invocation.wgUpload + for atomic.LoadInt32(&invocation.waitUploads) != 0 { + invocation.DoneUploadFile(err) + } + // release invocation.wgDone invocation.DoneRecvObj(err) } diff --git a/internal/client/remote-connection.go b/internal/client/remote-connection.go index 28baade..060b197 100644 --- a/internal/client/remote-connection.go +++ b/internal/client/remote-connection.go @@ -101,7 +101,8 @@ func (remote *RemoteConnection) StartCompilationSession(invocation *Invocation, // UploadFilesToRemote uploads files to the remote in parallel and finishes after all of them are done. func (remote *RemoteConnection) UploadFilesToRemote(invocation *Invocation, requiredFiles []*pb.FileMetadata, fileIndexesToUpload []uint32) error { - invocation.wgUpload.Add(len(fileIndexesToUpload)) + invocation.waitUploads = int32(len(fileIndexesToUpload)) + invocation.wgUpload.Add(int(invocation.waitUploads)) for _, fileIndex := range fileIndexesToUpload { remote.filesUploading.StartUploadingFileToRemote(invocation, requiredFiles[fileIndex], fileIndex) diff --git a/tests/dt/cmake1/src/empty.h b/tests/dt/cmake1/src/empty.h new file mode 100644 index 0000000..e69de29 diff --git a/tests/dt/cmake1/src/main.cpp b/tests/dt/cmake1/src/main.cpp index a719a37..3050d36 100644 --- a/tests/dt/cmake1/src/main.cpp +++ b/tests/dt/cmake1/src/main.cpp @@ -1,5 +1,6 @@ // main.cpp #include "all-headers.h" +#include "empty.h" int main() { std::cout << sum(1, 2) << std::endl;