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.

282 lines
8.5 KiB

4 years ago
  1. #if MYSQL_6_10
  2. // Copyright © 2009, 2016 Oracle and/or its affiliates. All rights reserved.
  3. //
  4. // MySQL Connector/NET is licensed under the terms of the GPLv2
  5. // <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
  6. // MySQL Connectors. There are special exceptions to the terms and
  7. // conditions of the GPLv2 as it is applied to this software, see the
  8. // FLOSS License Exception
  9. // <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
  10. //
  11. // This program is free software; you can redistribute it and/or modify
  12. // it under the terms of the GNU General Public License as published
  13. // by the Free Software Foundation; version 2 of the License.
  14. //
  15. // This program is distributed in the hope that it will be useful, but
  16. // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  17. // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  18. // for more details.
  19. //
  20. // You should have received a copy of the GNU General Public License along
  21. // with this program; if not, write to the Free Software Foundation, Inc.,
  22. // 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  23. using System;
  24. using System.IO;
  25. using Externals.MySql.Data.Common;
  26. namespace Externals.MySql.Data.MySqlClient
  27. {
  28. /// <summary>
  29. /// Stream that supports timeout of IO operations.
  30. /// This class is used is used to support timeouts for SQL command, where a
  31. /// typical operation involves several network reads/writes.
  32. /// Timeout here is defined as the accumulated duration of all IO operations.
  33. /// </summary>
  34. internal class TimedStream : Stream
  35. {
  36. readonly Stream _baseStream;
  37. int _timeout;
  38. int _lastReadTimeout;
  39. int _lastWriteTimeout;
  40. readonly LowResolutionStopwatch _stopwatch;
  41. internal bool IsClosed { get; private set; }
  42. enum IOKind
  43. {
  44. Read,
  45. Write
  46. };
  47. /// <summary>
  48. /// Construct a TimedStream
  49. /// </summary>
  50. /// <param name="baseStream"> Undelying stream</param>
  51. public TimedStream(Stream baseStream)
  52. {
  53. this._baseStream = baseStream;
  54. _timeout = baseStream.CanTimeout ? baseStream.ReadTimeout : System.Threading.Timeout.Infinite;
  55. IsClosed = false;
  56. _stopwatch = new LowResolutionStopwatch();
  57. }
  58. /// <summary>
  59. /// Figure out whether it is necessary to reset timeout on stream.
  60. /// We track the current value of timeout and try to avoid
  61. /// changing it too often, because setting Read/WriteTimeout property
  62. /// on network stream maybe a slow operation that involves a system call
  63. /// (setsockopt). Therefore, we allow a small difference, and do not
  64. /// reset timeout if current value is slightly greater than the requested
  65. /// one (within 0.1 second).
  66. /// </summary>
  67. private bool ShouldResetStreamTimeout(int currentValue, int newValue)
  68. {
  69. if (!_baseStream.CanTimeout) return false;
  70. if (newValue == System.Threading.Timeout.Infinite
  71. && currentValue != newValue)
  72. return true;
  73. if (newValue > currentValue)
  74. return true;
  75. return currentValue >= newValue + 100;
  76. }
  77. private void StartTimer(IOKind op)
  78. {
  79. int streamTimeout;
  80. if (_timeout == System.Threading.Timeout.Infinite)
  81. streamTimeout = System.Threading.Timeout.Infinite;
  82. else
  83. streamTimeout = _timeout - (int)_stopwatch.ElapsedMilliseconds;
  84. if (op == IOKind.Read)
  85. {
  86. if (ShouldResetStreamTimeout(_lastReadTimeout, streamTimeout))
  87. {
  88. _baseStream.ReadTimeout = streamTimeout;
  89. _lastReadTimeout = streamTimeout;
  90. }
  91. }
  92. else
  93. {
  94. if (ShouldResetStreamTimeout(_lastWriteTimeout, streamTimeout))
  95. {
  96. _baseStream.WriteTimeout = streamTimeout;
  97. _lastWriteTimeout = streamTimeout;
  98. }
  99. }
  100. if (_timeout == System.Threading.Timeout.Infinite)
  101. return;
  102. _stopwatch.Start();
  103. }
  104. private void StopTimer()
  105. {
  106. if (_timeout == System.Threading.Timeout.Infinite)
  107. return;
  108. _stopwatch.Stop();
  109. // Normally, a timeout exception would be thrown by stream itself,
  110. // since we set the read/write timeout for the stream. However
  111. // there is a gap between end of IO operation and stopping the
  112. // stop watch, and it makes it possible for timeout to exceed
  113. // even after IO completed successfully.
  114. if (_stopwatch.ElapsedMilliseconds > _timeout)
  115. {
  116. ResetTimeout(System.Threading.Timeout.Infinite);
  117. throw new TimeoutException("Timeout in IO operation");
  118. }
  119. }
  120. public override bool CanRead => _baseStream.CanRead;
  121. public override bool CanSeek => _baseStream.CanSeek;
  122. public override bool CanWrite => _baseStream.CanWrite;
  123. public override void Flush()
  124. {
  125. try
  126. {
  127. StartTimer(IOKind.Write);
  128. _baseStream.Flush();
  129. StopTimer();
  130. }
  131. catch (Exception e)
  132. {
  133. HandleException(e);
  134. throw;
  135. }
  136. }
  137. public override long Length => _baseStream.Length;
  138. public override long Position
  139. {
  140. get
  141. {
  142. return _baseStream.Position;
  143. }
  144. set
  145. {
  146. _baseStream.Position = value;
  147. }
  148. }
  149. public override int Read(byte[] buffer, int offset, int count)
  150. {
  151. try
  152. {
  153. StartTimer(IOKind.Read);
  154. int retval = _baseStream.Read(buffer, offset, count);
  155. StopTimer();
  156. return retval;
  157. }
  158. catch (Exception e)
  159. {
  160. HandleException(e);
  161. throw;
  162. }
  163. }
  164. public override int ReadByte()
  165. {
  166. try
  167. {
  168. StartTimer(IOKind.Read);
  169. int retval = _baseStream.ReadByte();
  170. StopTimer();
  171. return retval;
  172. }
  173. catch (Exception e)
  174. {
  175. HandleException(e);
  176. throw;
  177. }
  178. }
  179. public override long Seek(long offset, SeekOrigin origin)
  180. {
  181. return _baseStream.Seek(offset, origin);
  182. }
  183. public override void SetLength(long value)
  184. {
  185. _baseStream.SetLength(value);
  186. }
  187. public override void Write(byte[] buffer, int offset, int count)
  188. {
  189. try
  190. {
  191. StartTimer(IOKind.Write);
  192. _baseStream.Write(buffer, offset, count);
  193. StopTimer();
  194. }
  195. catch (Exception e)
  196. {
  197. HandleException(e);
  198. throw;
  199. }
  200. }
  201. public override bool CanTimeout => _baseStream.CanTimeout;
  202. public override int ReadTimeout
  203. {
  204. get { return _baseStream.ReadTimeout; }
  205. set { _baseStream.ReadTimeout = value; }
  206. }
  207. public override int WriteTimeout
  208. {
  209. get { return _baseStream.WriteTimeout; }
  210. set { _baseStream.WriteTimeout = value; }
  211. }
  212. #if NETSTANDARD1_3
  213. public void Close()
  214. #else
  215. public override void Close()
  216. #endif
  217. {
  218. if (IsClosed)
  219. return;
  220. IsClosed = true;
  221. #if !NETSTANDARD1_3
  222. _baseStream.Close();
  223. #endif
  224. _baseStream.Dispose();
  225. }
  226. public void ResetTimeout(int newTimeout)
  227. {
  228. if (newTimeout == System.Threading.Timeout.Infinite || newTimeout == 0)
  229. _timeout = System.Threading.Timeout.Infinite;
  230. else
  231. _timeout = newTimeout;
  232. _stopwatch.Reset();
  233. }
  234. /// <summary>
  235. /// Common handler for IO exceptions.
  236. /// Resets timeout to infinity if timeout exception is
  237. /// detected and stops the times.
  238. /// </summary>
  239. /// <param name="e">original exception</param>
  240. void HandleException(Exception e)
  241. {
  242. _stopwatch.Stop();
  243. ResetTimeout(-1);
  244. }
  245. }
  246. }
  247. #endif