You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

file.go 7.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. // +build windows
  2. package winio
  3. import (
  4. "errors"
  5. "io"
  6. "runtime"
  7. "sync"
  8. "sync/atomic"
  9. "syscall"
  10. "time"
  11. )
  12. //sys cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) = CancelIoEx
  13. //sys createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) = CreateIoCompletionPort
  14. //sys getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) = GetQueuedCompletionStatus
  15. //sys setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes
  16. //sys wsaGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) = ws2_32.WSAGetOverlappedResult
  17. type atomicBool int32
  18. func (b *atomicBool) isSet() bool { return atomic.LoadInt32((*int32)(b)) != 0 }
  19. func (b *atomicBool) setFalse() { atomic.StoreInt32((*int32)(b), 0) }
  20. func (b *atomicBool) setTrue() { atomic.StoreInt32((*int32)(b), 1) }
  21. func (b *atomicBool) swap(new bool) bool {
  22. var newInt int32
  23. if new {
  24. newInt = 1
  25. }
  26. return atomic.SwapInt32((*int32)(b), newInt) == 1
  27. }
  28. const (
  29. cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS = 1
  30. cFILE_SKIP_SET_EVENT_ON_HANDLE = 2
  31. )
  32. var (
  33. ErrFileClosed = errors.New("file has already been closed")
  34. ErrTimeout = &timeoutError{}
  35. )
  36. type timeoutError struct{}
  37. func (e *timeoutError) Error() string { return "i/o timeout" }
  38. func (e *timeoutError) Timeout() bool { return true }
  39. func (e *timeoutError) Temporary() bool { return true }
  40. type timeoutChan chan struct{}
  41. var ioInitOnce sync.Once
  42. var ioCompletionPort syscall.Handle
  43. // ioResult contains the result of an asynchronous IO operation
  44. type ioResult struct {
  45. bytes uint32
  46. err error
  47. }
  48. // ioOperation represents an outstanding asynchronous Win32 IO
  49. type ioOperation struct {
  50. o syscall.Overlapped
  51. ch chan ioResult
  52. }
  53. func initIo() {
  54. h, err := createIoCompletionPort(syscall.InvalidHandle, 0, 0, 0xffffffff)
  55. if err != nil {
  56. panic(err)
  57. }
  58. ioCompletionPort = h
  59. go ioCompletionProcessor(h)
  60. }
  61. // win32File implements Reader, Writer, and Closer on a Win32 handle without blocking in a syscall.
  62. // It takes ownership of this handle and will close it if it is garbage collected.
  63. type win32File struct {
  64. handle syscall.Handle
  65. wg sync.WaitGroup
  66. wgLock sync.RWMutex
  67. closing atomicBool
  68. socket bool
  69. readDeadline deadlineHandler
  70. writeDeadline deadlineHandler
  71. }
  72. type deadlineHandler struct {
  73. setLock sync.Mutex
  74. channel timeoutChan
  75. channelLock sync.RWMutex
  76. timer *time.Timer
  77. timedout atomicBool
  78. }
  79. // makeWin32File makes a new win32File from an existing file handle
  80. func makeWin32File(h syscall.Handle) (*win32File, error) {
  81. f := &win32File{handle: h}
  82. ioInitOnce.Do(initIo)
  83. _, err := createIoCompletionPort(h, ioCompletionPort, 0, 0xffffffff)
  84. if err != nil {
  85. return nil, err
  86. }
  87. err = setFileCompletionNotificationModes(h, cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS|cFILE_SKIP_SET_EVENT_ON_HANDLE)
  88. if err != nil {
  89. return nil, err
  90. }
  91. f.readDeadline.channel = make(timeoutChan)
  92. f.writeDeadline.channel = make(timeoutChan)
  93. return f, nil
  94. }
  95. func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) {
  96. // If we return the result of makeWin32File directly, it can result in an
  97. // interface-wrapped nil, rather than a nil interface value.
  98. f, err := makeWin32File(h)
  99. if err != nil {
  100. return nil, err
  101. }
  102. return f, nil
  103. }
  104. // closeHandle closes the resources associated with a Win32 handle
  105. func (f *win32File) closeHandle() {
  106. f.wgLock.Lock()
  107. // Atomically set that we are closing, releasing the resources only once.
  108. if !f.closing.swap(true) {
  109. f.wgLock.Unlock()
  110. // cancel all IO and wait for it to complete
  111. cancelIoEx(f.handle, nil)
  112. f.wg.Wait()
  113. // at this point, no new IO can start
  114. syscall.Close(f.handle)
  115. f.handle = 0
  116. } else {
  117. f.wgLock.Unlock()
  118. }
  119. }
  120. // Close closes a win32File.
  121. func (f *win32File) Close() error {
  122. f.closeHandle()
  123. return nil
  124. }
  125. // prepareIo prepares for a new IO operation.
  126. // The caller must call f.wg.Done() when the IO is finished, prior to Close() returning.
  127. func (f *win32File) prepareIo() (*ioOperation, error) {
  128. f.wgLock.RLock()
  129. if f.closing.isSet() {
  130. f.wgLock.RUnlock()
  131. return nil, ErrFileClosed
  132. }
  133. f.wg.Add(1)
  134. f.wgLock.RUnlock()
  135. c := &ioOperation{}
  136. c.ch = make(chan ioResult)
  137. return c, nil
  138. }
  139. // ioCompletionProcessor processes completed async IOs forever
  140. func ioCompletionProcessor(h syscall.Handle) {
  141. for {
  142. var bytes uint32
  143. var key uintptr
  144. var op *ioOperation
  145. err := getQueuedCompletionStatus(h, &bytes, &key, &op, syscall.INFINITE)
  146. if op == nil {
  147. panic(err)
  148. }
  149. op.ch <- ioResult{bytes, err}
  150. }
  151. }
  152. // asyncIo processes the return value from ReadFile or WriteFile, blocking until
  153. // the operation has actually completed.
  154. func (f *win32File) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, err error) (int, error) {
  155. if err != syscall.ERROR_IO_PENDING {
  156. return int(bytes), err
  157. }
  158. if f.closing.isSet() {
  159. cancelIoEx(f.handle, &c.o)
  160. }
  161. var timeout timeoutChan
  162. if d != nil {
  163. d.channelLock.Lock()
  164. timeout = d.channel
  165. d.channelLock.Unlock()
  166. }
  167. var r ioResult
  168. select {
  169. case r = <-c.ch:
  170. err = r.err
  171. if err == syscall.ERROR_OPERATION_ABORTED {
  172. if f.closing.isSet() {
  173. err = ErrFileClosed
  174. }
  175. } else if err != nil && f.socket {
  176. // err is from Win32. Query the overlapped structure to get the winsock error.
  177. var bytes, flags uint32
  178. err = wsaGetOverlappedResult(f.handle, &c.o, &bytes, false, &flags)
  179. }
  180. case <-timeout:
  181. cancelIoEx(f.handle, &c.o)
  182. r = <-c.ch
  183. err = r.err
  184. if err == syscall.ERROR_OPERATION_ABORTED {
  185. err = ErrTimeout
  186. }
  187. }
  188. // runtime.KeepAlive is needed, as c is passed via native
  189. // code to ioCompletionProcessor, c must remain alive
  190. // until the channel read is complete.
  191. runtime.KeepAlive(c)
  192. return int(r.bytes), err
  193. }
  194. // Read reads from a file handle.
  195. func (f *win32File) Read(b []byte) (int, error) {
  196. c, err := f.prepareIo()
  197. if err != nil {
  198. return 0, err
  199. }
  200. defer f.wg.Done()
  201. if f.readDeadline.timedout.isSet() {
  202. return 0, ErrTimeout
  203. }
  204. var bytes uint32
  205. err = syscall.ReadFile(f.handle, b, &bytes, &c.o)
  206. n, err := f.asyncIo(c, &f.readDeadline, bytes, err)
  207. runtime.KeepAlive(b)
  208. // Handle EOF conditions.
  209. if err == nil && n == 0 && len(b) != 0 {
  210. return 0, io.EOF
  211. } else if err == syscall.ERROR_BROKEN_PIPE {
  212. return 0, io.EOF
  213. } else {
  214. return n, err
  215. }
  216. }
  217. // Write writes to a file handle.
  218. func (f *win32File) Write(b []byte) (int, error) {
  219. c, err := f.prepareIo()
  220. if err != nil {
  221. return 0, err
  222. }
  223. defer f.wg.Done()
  224. if f.writeDeadline.timedout.isSet() {
  225. return 0, ErrTimeout
  226. }
  227. var bytes uint32
  228. err = syscall.WriteFile(f.handle, b, &bytes, &c.o)
  229. n, err := f.asyncIo(c, &f.writeDeadline, bytes, err)
  230. runtime.KeepAlive(b)
  231. return n, err
  232. }
  233. func (f *win32File) SetReadDeadline(deadline time.Time) error {
  234. return f.readDeadline.set(deadline)
  235. }
  236. func (f *win32File) SetWriteDeadline(deadline time.Time) error {
  237. return f.writeDeadline.set(deadline)
  238. }
  239. func (f *win32File) Flush() error {
  240. return syscall.FlushFileBuffers(f.handle)
  241. }
  242. func (f *win32File) Fd() uintptr {
  243. return uintptr(f.handle)
  244. }
  245. func (d *deadlineHandler) set(deadline time.Time) error {
  246. d.setLock.Lock()
  247. defer d.setLock.Unlock()
  248. if d.timer != nil {
  249. if !d.timer.Stop() {
  250. <-d.channel
  251. }
  252. d.timer = nil
  253. }
  254. d.timedout.setFalse()
  255. select {
  256. case <-d.channel:
  257. d.channelLock.Lock()
  258. d.channel = make(chan struct{})
  259. d.channelLock.Unlock()
  260. default:
  261. }
  262. if deadline.IsZero() {
  263. return nil
  264. }
  265. timeoutIO := func() {
  266. d.timedout.setTrue()
  267. close(d.channel)
  268. }
  269. now := time.Now()
  270. duration := deadline.Sub(now)
  271. if deadline.After(now) {
  272. // Deadline is in the future, set a timer to wait
  273. d.timer = time.AfterFunc(duration, timeoutIO)
  274. } else {
  275. // Deadline is in the past. Cancel all pending IO now.
  276. timeoutIO()
  277. }
  278. return nil
  279. }