Do

Merge lp:~felix-velasco/do/single-thread-reload into lp:do

Proposed by Félix Velasco
Status: Merged
Merged at revision: 1321
Proposed branch: lp:~felix-velasco/do/single-thread-reload
Merge into: lp: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:~felix-velasco/do/single-thread-reload
Reviewer Review Type Date Requested Status
Chris Halse Rogers Needs Fixing
Review via email: mp+15312@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Félix Velasco (felix-velasco) wrote :

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.

Revision history for this message
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!

review: Needs Fixing
Revision history for this message
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 SynchronizationLockException is raised. Wait () both releases the lock, and waits until the current thread can reacquire it, so other threads (i.e., Reload's) can acquire the lock and call Pulse ().

The universe_reload_requested boolean is a flag that allows a partial reload to abort, since a full universe reload has been requested, and will launch afterwards. I know it's not a very elegant solution, but it works.

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.

Revision history for this message
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_reload_requested into a ManualResetEvent
b) Everywhere where you check the status of universe_reload_requested, replace it with a .WaitOne with a 0 timeout, so it doesn't block.
c) Everywhere you set universe_reload_requested to true, replace with universe_reload_requested.Set ()
d) Everywhere you set universe_reload_requested to false, replace with universe_reload_requested.Reset ()
e) Instead of waiting on universe_monitor, universe_reload_requested.WaitOne (UpdateTimeout)

I *think* that would work; it seems you've got a better idea of C# threading than I have, though!

Revision history for this message
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

Revision history for this message
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 :).

Revision history for this message
Félix Velasco (felix-velasco) wrote :

Please go ahead with ManualResetEvent.

I read about the managed code leaving here (
http://www.yoda.arachsys.com/csharp/threads/waithandles.shtml), but I've
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://code.launchpad.net/~felix-velasco/do/single-thread-reload/+merge/15312
> You are the owner of lp:~felix-velasco/do/single-thread-reload.
>

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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 }