Timing out a lock attempt

xuanbao · · 707 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>With http handlers i&#39;ve often felt that Locks (ie, sync.Locker) are very dangerous. I fear them taking too long, and the http handler ending up timing out. As a concrete example, i&#39;m writing a web server that is using a RWMutex to protect a database of sorts. The database can be read concurrently, but if it&#39;s going to be written to, it gets truncated and cannot be read until the writing has completed. Requests to read that happen while writing should fail, informing the http caller to try again later. Writing obviously can&#39;t happen while reads are taking place either.</p> <p>If i use a standard RWMutex for the above example, i risk hanging for an unknown amount of time, and not returning a meaningful http response.</p> <p>Now, i&#39;m writing up a little structure to handle this and it doesn&#39;t seem complex, but it feels like this issue of locking from a web handler, and it being rather dangerous, must be fairly common no? So i thought i might ask here: How would <em>you</em> solve this? Does the Stdlib have a solution?</p> <p>After really putting thought to it, the <code>sync</code> package seems almost viral, in that if anything uses it i lose control of timing out, context, etc. It&#39;s a bit at odds with http focused code.</p> <p>Thoughts?</p> <hr/>**评论:**<br/><br/>9gPgEpW82IUTRbCzC5qr: <pre><p>you could just have another variable, maybe a boolean named &#34;isWriting&#34;, that you lock around.</p> <p>Then you readers can lock that var, check if &#34;isWriting&#34;, then unlock and send response</p></pre>throwlikepollock: <pre><p>Yea that&#39;s what I&#39;m doing in the short term. However being such a server focused language, it seems weird to me that <code>sync</code> has no <code>Context</code> support.</p></pre>l3pp4rd: <pre><p>use context.Context - WithDeadline (though your database must support context related lock timeout triggers), if your database has SQL type interface driver and supports database/sql/driver package, then whenever you do Exec do ExecContext instead. You may pass your context from net/http.Request down the chain with deadline. I suggest you to have a look at some sql driver implementation of ExecContext.</p> <p>The problem you are dealing with is natural and timeouts, synchronization of shared resources and locking, idempotency, must always be on your mind.</p></pre>throwlikepollock: <pre><blockquote> <p>The problem you are dealing with is natural and timeouts, synchronization of shared resources and locking, idempotency, must always be on your mind.</p> </blockquote> <p>Agreed, but it feels strange to me because the <code>sync</code> primitives don&#39;t offer any way to do this. I&#39;ve not found any cancel-able or timeout-able way to lock in the stdlib which seems like it something Go would be good at. Ie, Sync primitives with cancels/timeouts.</p></pre>KevBurnsJr: <pre><blockquote> <p>Requests to read that happen while writing should fail, informing the http caller to try again later.</p> </blockquote> <p>That doesn&#39;t sound like the ideal behavior and it doesn&#39;t sound like the solution you&#39;re talking about implementing. I&#39;m sure you&#39;d rather just wait until the writing is finished and then perform the read which is how RWMutex works except maybe you want to time out if it takes too long.</p> <p>Maybe you can explain specifically what behavior you want the http handler to exhibit?</p></pre>throwlikepollock: <pre><blockquote> <p>I&#39;m sure you&#39;d rather just wait until the writing is finished and then perform the read which is how RWMutex works except maybe you want to time out if it takes too long.</p> </blockquote> <p>Timing out is quite reasonable given my situation, yes. I chose the alternate behavior due to our requirements. Ie, the database is likely to take 30m+, so expecting to wait is silly, it&#39;s better to return the proper response from the start.</p> <p>But that&#39;s besides the point. The point is, I can&#39;t use <code>sync</code> at all, can I? RWMutex is <em>exactly</em> what I want, except it has a complete disregard for cancellation, and my HTTP handlers cannot craft a user experience.</p> <p>What I want, is some type of <code>RWMutextWithContext</code>. Thoughts?</p></pre>KevBurnsJr: <pre><p>Whatever it is you&#39;re doing, presumably starting a goroutine that waits for a ticker, is probably the correct solution.</p> <p>A general solution could be created which accepts a function to execute and a timeout.</p> <p>That&#39;s my thoughts.</p></pre>jerf: <pre><p>If you&#39;re not too worried about maximum efficiency at all costs, a condition variable can do it with another goroutine broadcasting after a timeout.</p> <p>If you read the source of that you may be able to specialize that farther, depending on your skill level.</p></pre>softwaregav: <pre><p>If you want to cancel the read operation after a set amount of time, you could just start that operation in a goroutine and wait for a result or your timeout. Basically, your read operation would be something like this:</p> <pre><code>type content struct { stuff string } type database struct { sync.RWMutex data map[int]content } func (d *database) Get(ctx context.Context, id int) (*content, error) { // Make a channel to send the result on result := make(chan *content) // Make a context to be canceled. ctx, cancel := context.WithCancel(ctx) defer cancel() // Start a goroutine to obtain a read lock and pass the result if the context hasn&#39;t been canceled. go d.read(ctx, id, result) var ( c *content err error ) select { case c = &lt;-result: case &lt;-time.After(time.Second * 5): err = errors.New(&#34;read operation taking too long&#34;) } return c, err } func (d *database) read(ctx context.Context, id int, result chan *content) { d.RLock() defer d.RUnlock() select { case &lt;-ctx.Done(): return default: c := d.data[id] result &lt;- &amp;c } } </code></pre> <p><a href="https://play.golang.org/p/3uTErRzvcJ" rel="nofollow">Here</a> is a working example on the playground.</p></pre>dchapes: <pre><p>Do readers have to fail during the write or can they just return old data?</p> <p>I think either can be implemented fairly straight forward using an <a href="https://golang.org/pkg/sync/atomic/#Value" rel="nofollow"><code>sync/atomic.Value</code></a>.</p></pre>

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

707 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传