Merge lp://staging/~felix-velasco/do/single-thread-reload into lp://staging/do
- single-thread-reload
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 1321 |
Proposed branch: | lp://staging/~felix-velasco/do/single-thread-reload |
Merge into: | lp://staging/do |
Diff against target: |
137 lines (+47/-29) 3 files modified
Do.Platform.Linux/src/Do.Universe/ApplicationItem.cs (+15/-18) Do.Platform.Linux/src/Do.Universe/CategoryItem.cs (+3/-6) Do/src/Do.Core/UniverseManager.cs (+29/-5) |
To merge this branch: | bzr merge lp://staging/~felix-velasco/do/single-thread-reload |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Chris Halse Rogers | Needs Fixing | ||
Review via email: mp+15312@code.staging.launchpad.net |
Commit message
Description of the change
Félix Velasco (felix-velasco) wrote : | # |
Chris Halse Rogers (raof) wrote : | # |
Sorry for taking so long to get around to looking at this.
The idea behind this patch is good, but I don't think that this implementation using Monitor.Pulse is correct. As I read it, Reload () is always going to block until UniverseUpdateLoop () is active - at which point, Pulsing the lock is not very useful, because nothing's waiting for it.
Because you're waiting on universe_monitor while holding the lock on universe_monitor, Update () isn't going to be able to acquire the lock on universe_monitor until UniverseUpdateLoop () has finished waiting (ie: has hit UpdateTimeout) and released the lock.
I think what you should be using is an AutoResetEvent - that doesn't require an extra lock or boolean, and you can wait on it in the same way as you're currently waiting on the lock.
Because it's been so long since you asked for this review, I'll merge this and make the changes if I don't hear from you in a couple of days.
Sorry again for the delay in review!
Félix Velasco (felix-velasco) wrote : | # |
First of all, I had almost forget this code, and I had lost any hope of it ever being merged. Thanks for reviewing after so long!
Then again, I think you didn't read it well. Reload () is never blocked by UniverseUpdateLoop (). In order to invoke Wait () or PulseAll (), you _need_ to own the lock, or a Synchronization
The universe_
I haven't worked with AutoResetEvent and, from what I've seen in the docs, I haven't find a way that would prevent the use of the flag. Could you please elaborate? I'd love finding a nicer way to do it.
Chris Halse Rogers (raof) wrote : | # |
Yeah; when I woke up this morning I thought “I need to look into managed threading more - does Wait release the lock or something”? Thanks for increasing my knowledge of C# thread constructs :).
On further thought, an AutoResetEvent wouldn't work, but a ManualResetEvent should. That would entail:
a) Dropping the universe_monitor & turning universe_
b) Everywhere where you check the status of universe_
c) Everywhere you set universe_
d) Everywhere you set universe_
e) Instead of waiting on universe_monitor, universe_
I *think* that would work; it seems you've got a better idea of C# threading than I have, though!
Félix Velasco (felix-velasco) wrote : | # |
I've been looking at the ManualResetEvent documentation, and the biggest problem I see in using it is that it leaves managed code for execution. A WaitOne call, even one with no timeout, is an expensive operation, specially if we compare it to checking a boolean.
Both solutions apparently provide the same functionality, but I see no benefits in using ManualResetEvent. All in all, it's your call :D
Chris Halse Rogers (raof) wrote : | # |
I'm a fan of the ManualResetEvent. I feel that it's simpler - you've
got just the one object instead of two, and I'm more familiar with it.
I don't think that the leaving managed code for execution part is a
problem[1]. That's really an implementation detail. Similarly, the
performance doesn't concern me - we'll be calling WaitOne about 3 times
in 5 minutes, and from a non-GUI thread. Each WaitOne could take on the
order of *seconds* and we wouldn't have a serious performance
problem :).
If there aren't any other benefits to the locking + Wait + Pulse method,
I'd prefer to go with the ManualResetEvent. That's more familiar to me,
I think it's intent is clearer, and getting threading right is already
difficult enough :).
[1] Incidentally, where in the docs do you get this? I see that
WaitHandles can be used to wrap native objects, but it's not obvious to
me that it necessarily needs to. After all, *all* threading is
eventually going to need to leave managed code because it'll using OS
threads. You're obviously knowledgeable about managed threading, so any
info you'd like to disseminate would be cool :).
Félix Velasco (felix-velasco) wrote : | # |
Please go ahead with ManualResetEvent.
I read about the managed code leaving here (
http://
been unable to find any similar information anywhere else, so I don't think
it can be trust. My fault.
El 7 de abril de 2010 02:12, Chris Halse Rogers <email address hidden> escribió:
> I'm a fan of the ManualResetEvent. I feel that it's simpler - you've
> got just the one object instead of two, and I'm more familiar with it.
>
> I don't think that the leaving managed code for execution part is a
> problem[1]. That's really an implementation detail. Similarly, the
> performance doesn't concern me - we'll be calling WaitOne about 3 times
> in 5 minutes, and from a non-GUI thread. Each WaitOne could take on the
> order of *seconds* and we wouldn't have a serious performance
> problem :).
>
> If there aren't any other benefits to the locking + Wait + Pulse method,
> I'd prefer to go with the ManualResetEvent. That's more familiar to me,
> I think it's intent is clearer, and getting threading right is already
> difficult enough :).
>
> [1] Incidentally, where in the docs do you get this? I see that
> WaitHandles can be used to wrap native objects, but it's not obvious to
> me that it necessarily needs to. After all, *all* threading is
> eventually going to need to leave managed code because it'll using OS
> threads. You're obviously knowledgeable about managed threading, so any
> info you'd like to disseminate would be cool :).
>
> --
>
> https:/
> You are the owner of lp:~felix-velasco/do/single-thread-reload.
>
Preview Diff
1 | === modified file 'Do.Platform.Linux/src/Do.Universe/ApplicationItem.cs' |
2 | --- Do.Platform.Linux/src/Do.Universe/ApplicationItem.cs 2009-11-19 20:52:16 +0000 |
3 | +++ Do.Platform.Linux/src/Do.Universe/ApplicationItem.cs 2009-11-27 10:50:26 +0000 |
4 | @@ -50,25 +50,22 @@ |
5 | |
6 | if (path == null) throw new ArgumentNullException ("path"); |
7 | |
8 | - lock (Instances) |
9 | - { |
10 | - if (Instances.ContainsKey (key)) { |
11 | - appItem = Instances [key]; |
12 | - } else { |
13 | - DesktopItem item = null; |
14 | - try { |
15 | - item = DesktopItem.NewFromFile (path, 0); |
16 | - appItem = new ApplicationItem (item); |
17 | - } catch (Exception e) { |
18 | - appItem = null; |
19 | - try { item.Dispose (); } catch { } |
20 | - Log.Error ("Could not load desktop item: {0}", e.Message); |
21 | - Log.Debug (e.StackTrace); |
22 | - } |
23 | - |
24 | - if (appItem != null) |
25 | - Instances [key] = appItem; |
26 | + if (Instances.ContainsKey (key)) { |
27 | + appItem = Instances [key]; |
28 | + } else { |
29 | + DesktopItem item = null; |
30 | + try { |
31 | + item = DesktopItem.NewFromFile (path, 0); |
32 | + appItem = new ApplicationItem (item); |
33 | + } catch (Exception e) { |
34 | + appItem = null; |
35 | + try { item.Dispose (); } catch { } |
36 | + Log.Error ("Could not load desktop item: {0}", e.Message); |
37 | + Log.Debug (e.StackTrace); |
38 | } |
39 | + |
40 | + if (appItem != null) |
41 | + Instances [key] = appItem; |
42 | } |
43 | return appItem; |
44 | } |
45 | |
46 | === modified file 'Do.Platform.Linux/src/Do.Universe/CategoryItem.cs' |
47 | --- Do.Platform.Linux/src/Do.Universe/CategoryItem.cs 2009-11-19 20:52:16 +0000 |
48 | +++ Do.Platform.Linux/src/Do.Universe/CategoryItem.cs 2009-11-27 10:50:26 +0000 |
49 | @@ -48,12 +48,9 @@ |
50 | public static CategoryItem GetCategoryItem (string category) |
51 | { |
52 | string lowCat = category.ToLower (); |
53 | - lock (Instances) |
54 | - { |
55 | - if (!Instances.ContainsKey (lowCat)) { |
56 | - CategoryItem item = new CategoryItem (category); |
57 | - Instances [lowCat] = item; |
58 | - } |
59 | + if (!Instances.ContainsKey (lowCat)) { |
60 | + CategoryItem item = new CategoryItem (category); |
61 | + Instances [lowCat] = item; |
62 | } |
63 | return Instances [lowCat]; |
64 | } |
65 | |
66 | === modified file 'Do/src/Do.Core/UniverseManager.cs' |
67 | --- Do/src/Do.Core/UniverseManager.cs 2009-06-24 22:42:51 +0000 |
68 | +++ Do/src/Do.Core/UniverseManager.cs 2009-11-27 10:50:26 +0000 |
69 | @@ -40,6 +40,8 @@ |
70 | UniverseCollection universe; |
71 | EventHandler initialized; |
72 | object universe_lock; |
73 | + object universe_monitor; |
74 | + volatile bool universe_reload_requested; |
75 | |
76 | const float epsilon = 0.00001f; |
77 | |
78 | @@ -80,6 +82,8 @@ |
79 | { |
80 | universe = new UniverseCollection (); |
81 | universe_lock = new object (); |
82 | + universe_monitor = new object (); |
83 | + universe_reload_requested = false; |
84 | |
85 | update_thread = new Thread (new ThreadStart (UniverseUpdateLoop)); |
86 | update_thread.IsBackground = true; |
87 | @@ -165,17 +169,33 @@ |
88 | DateTime startUpdate = DateTime.UtcNow; |
89 | |
90 | while (true) { |
91 | - Thread.Sleep (UpdateTimeout); |
92 | + lock (universe_monitor) { |
93 | + if (!universe_reload_requested) { |
94 | + Monitor.Wait (universe_monitor, UpdateTimeout); |
95 | + } |
96 | + } |
97 | + |
98 | if (Do.Controller.IsSummoned) continue; |
99 | startUpdate = DateTime.UtcNow; |
100 | - |
101 | + |
102 | + if (universe_reload_requested) { |
103 | + universe_reload_requested = false; |
104 | + ReloadUniverse (); |
105 | + continue; |
106 | + } |
107 | + |
108 | if (rand.Next (10) == 0) { |
109 | ReloadActions (universe); |
110 | + if (universe_reload_requested) |
111 | + continue; |
112 | } |
113 | - |
114 | + |
115 | foreach (ItemSource source in PluginManager.ItemSources) { |
116 | ReloadSource (source, universe); |
117 | - |
118 | + |
119 | + if (universe_reload_requested) |
120 | + break; |
121 | + |
122 | if (UpdateRunTime < DateTime.UtcNow - startUpdate) { |
123 | Thread.Sleep (UpdateTimeout); |
124 | // sleeping for a bit |
125 | @@ -308,7 +328,11 @@ |
126 | /// </summary> |
127 | public void Reload () |
128 | { |
129 | - Services.Application.RunOnThread (ReloadUniverse); |
130 | + universe_reload_requested = true; |
131 | + Log<UniverseManager>.Info ("Requesting universe reload"); |
132 | + lock (universe_monitor) { |
133 | + Monitor.PulseAll (universe_monitor); |
134 | + } |
135 | } |
136 | } |
137 | } |
This is a cleaner fix to the 100% CPU bug.
Whenever a full reload is requested, it gets scheduled in the update thread, instead of starting a new one. If the thread is reloading, it aborts it and begins a full reload.