aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/ClientStack/Linden/UDP
diff options
context:
space:
mode:
authorJustin Clark-Casey (justincc)2015-01-12 20:56:37 +0000
committerJustin Clark-Casey (justincc)2015-01-12 20:56:37 +0000
commit8e1e8a0920a9e94305619e9afb8e053b4daefb89 (patch)
treecc6cb595807393c465f520cd1a1fc7a8aab33b2d /OpenSim/Region/ClientStack/Linden/UDP
parentSimulatorFeatures: the viewer also takes GridName in OpenSim extras. Added th... (diff)
downloadopensim-SC_OLD-8e1e8a0920a9e94305619e9afb8e053b4daefb89.zip
opensim-SC_OLD-8e1e8a0920a9e94305619e9afb8e053b4daefb89.tar.gz
opensim-SC_OLD-8e1e8a0920a9e94305619e9afb8e053b4daefb89.tar.bz2
opensim-SC_OLD-8e1e8a0920a9e94305619e9afb8e053b4daefb89.tar.xz
Make the performance controlling job processing threads introduced in conference code use a generic JobEngine class rather than 4 slightly different copy/pasted versions.
Diffstat (limited to 'OpenSim/Region/ClientStack/Linden/UDP')
-rw-r--r--OpenSim/Region/ClientStack/Linden/UDP/IncomingPacketAsyncHandlingEngine.cs328
-rw-r--r--OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs5
-rw-r--r--OpenSim/Region/ClientStack/Linden/UDP/LLUDPClient.cs2
-rw-r--r--OpenSim/Region/ClientStack/Linden/UDP/LLUDPServer.cs52
-rw-r--r--OpenSim/Region/ClientStack/Linden/UDP/LLUDPServerCommands.cs46
-rw-r--r--OpenSim/Region/ClientStack/Linden/UDP/OutgoingQueueRefillEngine.cs288
6 files changed, 92 insertions, 629 deletions
diff --git a/OpenSim/Region/ClientStack/Linden/UDP/IncomingPacketAsyncHandlingEngine.cs b/OpenSim/Region/ClientStack/Linden/UDP/IncomingPacketAsyncHandlingEngine.cs
deleted file mode 100644
index 6f40b24..0000000
--- a/OpenSim/Region/ClientStack/Linden/UDP/IncomingPacketAsyncHandlingEngine.cs
+++ /dev/null
@@ -1,328 +0,0 @@
1/*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSimulator Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28using System;
29using System.Collections.Concurrent;
30using System.Reflection;
31using System.Threading;
32using log4net;
33using OpenSim.Framework;
34using OpenSim.Framework.Monitoring;
35using OpenSim.Region.Framework.Scenes;
36
37namespace OpenSim.Region.ClientStack.LindenUDP
38{
39 public class Job
40 {
41 public string Name;
42 public WaitCallback Callback;
43 public object O;
44
45 public Job(string name, WaitCallback callback, object o)
46 {
47 Name = name;
48 Callback = callback;
49 O = o;
50 }
51 }
52
53 // TODO: These kinds of classes MUST be generalized with JobEngine, etc.
54 public class IncomingPacketAsyncHandlingEngine
55 {
56 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
57
58 public int LogLevel { get; set; }
59
60 public bool IsRunning { get; private set; }
61
62 /// <summary>
63 /// The timeout in milliseconds to wait for at least one event to be written when the recorder is stopping.
64 /// </summary>
65 public int RequestProcessTimeoutOnStop { get; set; }
66
67 /// <summary>
68 /// Controls whether we need to warn in the log about exceeding the max queue size.
69 /// </summary>
70 /// <remarks>
71 /// This is flipped to false once queue max has been exceeded and back to true when it falls below max, in
72 /// order to avoid spamming the log with lots of warnings.
73 /// </remarks>
74 private bool m_warnOverMaxQueue = true;
75
76 private BlockingCollection<Job> m_requestQueue;
77
78 private CancellationTokenSource m_cancelSource = new CancellationTokenSource();
79
80 private LLUDPServer m_udpServer;
81
82 private Stat m_requestsWaitingStat;
83
84 private Job m_currentJob;
85
86 /// <summary>
87 /// Used to signal that we are ready to complete stop.
88 /// </summary>
89 private ManualResetEvent m_finishedProcessingAfterStop = new ManualResetEvent(false);
90
91 public IncomingPacketAsyncHandlingEngine(LLUDPServer server)
92 {
93 //LogLevel = 1;
94 m_udpServer = server;
95 RequestProcessTimeoutOnStop = 5000;
96
97 // MainConsole.Instance.Commands.AddCommand(
98 // "Debug",
99 // false,
100 // "debug jobengine",
101 // "debug jobengine <start|stop|status>",
102 // "Start, stop or get status of the job engine.",
103 // "If stopped then all jobs are processed immediately.",
104 // HandleControlCommand);
105 }
106
107 public void Start()
108 {
109 lock (this)
110 {
111 if (IsRunning)
112 return;
113
114 IsRunning = true;
115
116 m_finishedProcessingAfterStop.Reset();
117
118 m_requestQueue = new BlockingCollection<Job>(new ConcurrentQueue<Job>(), 5000);
119
120 m_requestsWaitingStat =
121 new Stat(
122 "IncomingPacketAsyncRequestsWaiting",
123 "Number of incoming packets waiting for async processing in engine.",
124 "",
125 "",
126 "clientstack",
127 m_udpServer.Scene.Name,
128 StatType.Pull,
129 MeasuresOfInterest.None,
130 stat => stat.Value = m_requestQueue.Count,
131 StatVerbosity.Debug);
132
133 StatsManager.RegisterStat(m_requestsWaitingStat);
134
135 WorkManager.StartThread(
136 ProcessRequests,
137 string.Format("Incoming Packet Async Handling Engine Thread ({0})", m_udpServer.Scene.Name),
138 ThreadPriority.Normal,
139 false,
140 true,
141 null,
142 int.MaxValue);
143 }
144 }
145
146 public void Stop()
147 {
148 lock (this)
149 {
150 try
151 {
152 if (!IsRunning)
153 return;
154
155 IsRunning = false;
156
157 int requestsLeft = m_requestQueue.Count;
158
159 if (requestsLeft <= 0)
160 {
161 m_cancelSource.Cancel();
162 }
163 else
164 {
165 m_log.InfoFormat("[INCOMING PACKET ASYNC HANDLING ENGINE]: Waiting to write {0} events after stop.", requestsLeft);
166
167 while (requestsLeft > 0)
168 {
169 if (!m_finishedProcessingAfterStop.WaitOne(RequestProcessTimeoutOnStop))
170 {
171 // After timeout no events have been written
172 if (requestsLeft == m_requestQueue.Count)
173 {
174 m_log.WarnFormat(
175 "[INCOMING PACKET ASYNC HANDLING ENGINE]: No requests processed after {0} ms wait. Discarding remaining {1} requests",
176 RequestProcessTimeoutOnStop, requestsLeft);
177
178 break;
179 }
180 }
181
182 requestsLeft = m_requestQueue.Count;
183 }
184 }
185 }
186 finally
187 {
188 m_cancelSource.Dispose();
189 StatsManager.DeregisterStat(m_requestsWaitingStat);
190 m_requestsWaitingStat = null;
191 m_requestQueue = null;
192 }
193 }
194 }
195
196 public bool QueueRequest(string name, WaitCallback req, object o)
197 {
198 if (LogLevel >= 1)
199 m_log.DebugFormat("[INCOMING PACKET ASYNC HANDLING ENGINE]: Queued job {0}", name);
200
201 if (m_requestQueue.Count < m_requestQueue.BoundedCapacity)
202 {
203 // m_log.DebugFormat(
204 // "[OUTGOING QUEUE REFILL ENGINE]: Adding request for categories {0} for {1} in {2}",
205 // categories, client.AgentID, m_udpServer.Scene.Name);
206
207 m_requestQueue.Add(new Job(name, req, o));
208
209 if (!m_warnOverMaxQueue)
210 m_warnOverMaxQueue = true;
211
212 return true;
213 }
214 else
215 {
216 if (m_warnOverMaxQueue)
217 {
218 // m_log.WarnFormat(
219 // "[JOB ENGINE]: Request queue at maximum capacity, not recording request from {0} in {1}",
220 // client.AgentID, m_udpServer.Scene.Name);
221
222 m_log.WarnFormat("[INCOMING PACKET ASYNC HANDLING ENGINE]: Request queue at maximum capacity, not recording job");
223
224 m_warnOverMaxQueue = false;
225 }
226
227 return false;
228 }
229 }
230
231 private void ProcessRequests()
232 {
233 try
234 {
235 while (IsRunning || m_requestQueue.Count > 0)
236 {
237 m_currentJob = m_requestQueue.Take(m_cancelSource.Token);
238
239 // QueueEmpty callback = req.Client.OnQueueEmpty;
240 //
241 // if (callback != null)
242 // {
243 // try
244 // {
245 // callback(req.Categories);
246 // }
247 // catch (Exception e)
248 // {
249 // m_log.Error("[OUTGOING QUEUE REFILL ENGINE]: ProcessRequests(" + req.Categories + ") threw an exception: " + e.Message, e);
250 // }
251 // }
252
253 if (LogLevel >= 1)
254 m_log.DebugFormat("[INCOMING PACKET ASYNC HANDLING ENGINE]: Processing job {0}", m_currentJob.Name);
255
256 try
257 {
258 m_currentJob.Callback.Invoke(m_currentJob.O);
259 }
260 catch (Exception e)
261 {
262 m_log.Error(
263 string.Format(
264 "[INCOMING PACKET ASYNC HANDLING ENGINE]: Job {0} failed, continuing. Exception ", m_currentJob.Name), e);
265 }
266
267 if (LogLevel >= 1)
268 m_log.DebugFormat("[INCOMING PACKET ASYNC HANDLING ENGINE]: Processed job {0}", m_currentJob.Name);
269
270 m_currentJob = null;
271 }
272 }
273 catch (OperationCanceledException)
274 {
275 }
276
277 m_finishedProcessingAfterStop.Set();
278 }
279
280 // private void HandleControlCommand(string module, string[] args)
281 // {
282 // // if (SceneManager.Instance.CurrentScene != null && SceneManager.Instance.CurrentScene != m_udpServer.Scene)
283 // // return;
284 //
285 // if (args.Length < 3)
286 // {
287 // MainConsole.Instance.Output("Usage: debug jobengine <stop|start|status|loglevel>");
288 // return;
289 // }
290 //
291 // string subCommand = args[2];
292 //
293 // if (subCommand == "stop")
294 // {
295 // Stop();
296 // MainConsole.Instance.OutputFormat("Stopped job engine.");
297 // }
298 // else if (subCommand == "start")
299 // {
300 // Start();
301 // MainConsole.Instance.OutputFormat("Started job engine.");
302 // }
303 // else if (subCommand == "status")
304 // {
305 // MainConsole.Instance.OutputFormat("Job engine running: {0}", IsRunning);
306 // MainConsole.Instance.OutputFormat("Current job {0}", m_currentJob != null ? m_currentJob.Name : "none");
307 // MainConsole.Instance.OutputFormat(
308 // "Jobs waiting: {0}", IsRunning ? m_requestQueue.Count.ToString() : "n/a");
309 // MainConsole.Instance.OutputFormat("Log Level: {0}", LogLevel);
310 // }
311 //
312 // else if (subCommand == "loglevel")
313 // {
314 // // int logLevel;
315 // int logLevel = int.Parse(args[3]);
316 // // if (ConsoleUtil.TryParseConsoleInt(MainConsole.Instance, args[4], out logLevel))
317 // // {
318 // LogLevel = logLevel;
319 // MainConsole.Instance.OutputFormat("Set log level to {0}", LogLevel);
320 // // }
321 // }
322 // else
323 // {
324 // MainConsole.Instance.OutputFormat("Unrecognized job engine subcommand {0}", subCommand);
325 // }
326 // }
327 }
328}
diff --git a/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs b/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs
index 5da0ca1..bb4f8a7 100644
--- a/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs
+++ b/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs
@@ -724,10 +724,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
724 object obj = new AsyncPacketProcess(this, pprocessor.method, packet); 724 object obj = new AsyncPacketProcess(this, pprocessor.method, packet);
725 725
726 if (pprocessor.InEngine) 726 if (pprocessor.InEngine)
727 m_udpServer.IpahEngine.QueueRequest( 727 m_udpServer.IpahEngine.QueueJob(packet.Type.ToString(), () => ProcessSpecificPacketAsync(obj));
728 packet.Type.ToString(),
729 ProcessSpecificPacketAsync,
730 obj);
731 else 728 else
732 Util.FireAndForget(ProcessSpecificPacketAsync, obj, packet.Type.ToString()); 729 Util.FireAndForget(ProcessSpecificPacketAsync, obj, packet.Type.ToString());
733 730
diff --git a/OpenSim/Region/ClientStack/Linden/UDP/LLUDPClient.cs b/OpenSim/Region/ClientStack/Linden/UDP/LLUDPClient.cs
index de91856..ce6e3ee 100644
--- a/OpenSim/Region/ClientStack/Linden/UDP/LLUDPClient.cs
+++ b/OpenSim/Region/ClientStack/Linden/UDP/LLUDPClient.cs
@@ -736,7 +736,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
736 } 736 }
737 else 737 else
738 { 738 {
739 m_udpServer.OqrEngine.QueueRequest(this, categories); 739 m_udpServer.OqrEngine.QueueJob(AgentID.ToString(), () => FireQueueEmpty(categories));
740 } 740 }
741 } 741 }
742 else 742 else
diff --git a/OpenSim/Region/ClientStack/Linden/UDP/LLUDPServer.cs b/OpenSim/Region/ClientStack/Linden/UDP/LLUDPServer.cs
index 2f97516..7bd16e6 100644
--- a/OpenSim/Region/ClientStack/Linden/UDP/LLUDPServer.cs
+++ b/OpenSim/Region/ClientStack/Linden/UDP/LLUDPServer.cs
@@ -367,14 +367,17 @@ namespace OpenSim.Region.ClientStack.LindenUDP
367 /// Queue some low priority but potentially high volume async requests so that they don't overwhelm available 367 /// Queue some low priority but potentially high volume async requests so that they don't overwhelm available
368 /// threadpool threads. 368 /// threadpool threads.
369 /// </summary> 369 /// </summary>
370 public IncomingPacketAsyncHandlingEngine IpahEngine { get; private set; } 370 public JobEngine IpahEngine { get; private set; }
371 371
372 /// <summary> 372 /// <summary>
373 /// Experimental facility to run queue empty processing within a controlled number of threads rather than 373 /// Run queue empty processing within a single persistent thread.
374 /// requiring massive numbers of short-lived threads from the threadpool when there are a high number of
375 /// connections.
376 /// </summary> 374 /// </summary>
377 public OutgoingQueueRefillEngine OqrEngine { get; private set; } 375 /// <remarks>
376 /// This is the alternative to having every
377 /// connection schedule its own job in the threadpool which causes performance problems when there are many
378 /// connections.
379 /// </remarks>
380 public JobEngine OqrEngine { get; private set; }
378 381
379 public LLUDPServer( 382 public LLUDPServer(
380 IPAddress listenIP, ref uint port, int proxyPortOffsetParm, bool allow_alternate_port, 383 IPAddress listenIP, ref uint port, int proxyPortOffsetParm, bool allow_alternate_port,
@@ -459,9 +462,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP
459 462
460 if (usePools) 463 if (usePools)
461 EnablePools(); 464 EnablePools();
462
463 IpahEngine = new IncomingPacketAsyncHandlingEngine(this);
464 OqrEngine = new OutgoingQueueRefillEngine(this);
465 } 465 }
466 466
467 public void Start() 467 public void Start()
@@ -633,6 +633,16 @@ namespace OpenSim.Region.ClientStack.LindenUDP
633 633
634 Scene = (Scene)scene; 634 Scene = (Scene)scene;
635 m_location = new Location(Scene.RegionInfo.RegionHandle); 635 m_location = new Location(Scene.RegionInfo.RegionHandle);
636
637 IpahEngine
638 = new JobEngine(
639 string.Format("Incoming Packet Async Handling Engine ({0})", Scene.Name),
640 "INCOMING PACKET ASYNC HANDLING ENGINE");
641
642 OqrEngine
643 = new JobEngine(
644 string.Format("Outgoing Queue Refill Engine ({0})", Scene.Name),
645 "OUTGOING QUEUE REFILL ENGINE");
636 646
637 StatsManager.RegisterStat( 647 StatsManager.RegisterStat(
638 new Stat( 648 new Stat(
@@ -713,6 +723,32 @@ namespace OpenSim.Region.ClientStack.LindenUDP
713 MeasuresOfInterest.AverageChangeOverTime, 723 MeasuresOfInterest.AverageChangeOverTime,
714 stat => stat.Value = GetTotalQueuedOutgoingPackets(), 724 stat => stat.Value = GetTotalQueuedOutgoingPackets(),
715 StatVerbosity.Info)); 725 StatVerbosity.Info));
726
727 StatsManager.RegisterStat(
728 new Stat(
729 "IncomingPacketAsyncRequestsWaiting",
730 "Number of incoming packets waiting for async processing in engine.",
731 "",
732 "",
733 "clientstack",
734 Scene.Name,
735 StatType.Pull,
736 MeasuresOfInterest.None,
737 stat => stat.Value = IpahEngine.JobsWaiting,
738 StatVerbosity.Debug));
739
740 StatsManager.RegisterStat(
741 new Stat(
742 "OQRERequestsWaiting",
743 "Number of outgong queue refill requests waiting for processing.",
744 "",
745 "",
746 "clientstack",
747 Scene.Name,
748 StatType.Pull,
749 MeasuresOfInterest.None,
750 stat => stat.Value = OqrEngine.JobsWaiting,
751 StatVerbosity.Debug));
716 752
717 // We delay enabling pool stats to AddScene() instead of Initialize() so that we can distinguish pool stats by 753 // We delay enabling pool stats to AddScene() instead of Initialize() so that we can distinguish pool stats by
718 // scene name 754 // scene name
diff --git a/OpenSim/Region/ClientStack/Linden/UDP/LLUDPServerCommands.cs b/OpenSim/Region/ClientStack/Linden/UDP/LLUDPServerCommands.cs
index e0398d5..17a394d 100644
--- a/OpenSim/Region/ClientStack/Linden/UDP/LLUDPServerCommands.cs
+++ b/OpenSim/Region/ClientStack/Linden/UDP/LLUDPServerCommands.cs
@@ -186,6 +186,15 @@ namespace OpenSim.Region.ClientStack.LindenUDP
186 "debug lludp toggle agentupdate", 186 "debug lludp toggle agentupdate",
187 "Toggle whether agentupdate packets are processed or simply discarded.", 187 "Toggle whether agentupdate packets are processed or simply discarded.",
188 HandleAgentUpdateCommand); 188 HandleAgentUpdateCommand);
189
190 MainConsole.Instance.Commands.AddCommand(
191 "Debug",
192 false,
193 "debug lludp oqre",
194 "debug lludp oqre <start|stop|status>",
195 "Start, stop or get status of OutgoingQueueRefillEngine.",
196 "If stopped then refill requests are processed directly via the threadpool.",
197 HandleOqreCommand);
189 } 198 }
190 199
191 private void HandleShowServerThrottlesCommand(string module, string[] args) 200 private void HandleShowServerThrottlesCommand(string module, string[] args)
@@ -758,5 +767,42 @@ namespace OpenSim.Region.ClientStack.LindenUDP
758 MainConsole.Instance.OutputFormat( 767 MainConsole.Instance.OutputFormat(
759 "Packet debug level for new clients is {0}", m_udpServer.DefaultClientPacketDebugLevel); 768 "Packet debug level for new clients is {0}", m_udpServer.DefaultClientPacketDebugLevel);
760 } 769 }
770
771 private void HandleOqreCommand(string module, string[] args)
772 {
773 if (SceneManager.Instance.CurrentScene != null && SceneManager.Instance.CurrentScene != m_udpServer.Scene)
774 return;
775
776 if (args.Length != 4)
777 {
778 MainConsole.Instance.Output("Usage: debug lludp oqre <stop|start|status>");
779 return;
780 }
781
782 string subCommand = args[3];
783
784 if (subCommand == "stop")
785 {
786 m_udpServer.OqrEngine.Stop();
787 MainConsole.Instance.OutputFormat("Stopped OQRE for {0}", m_udpServer.Scene.Name);
788 }
789 else if (subCommand == "start")
790 {
791 m_udpServer.OqrEngine.Start();
792 MainConsole.Instance.OutputFormat("Started OQRE for {0}", m_udpServer.Scene.Name);
793 }
794 else if (subCommand == "status")
795 {
796 MainConsole.Instance.OutputFormat("OQRE in {0}", m_udpServer.Scene.Name);
797 MainConsole.Instance.OutputFormat("Running: {0}", m_udpServer.OqrEngine.IsRunning);
798 MainConsole.Instance.OutputFormat(
799 "Requests waiting: {0}",
800 m_udpServer.OqrEngine.IsRunning ? m_udpServer.OqrEngine.JobsWaiting.ToString() : "n/a");
801 }
802 else
803 {
804 MainConsole.Instance.OutputFormat("Unrecognized OQRE subcommand {0}", subCommand);
805 }
806 }
761 } 807 }
762} \ No newline at end of file 808} \ No newline at end of file
diff --git a/OpenSim/Region/ClientStack/Linden/UDP/OutgoingQueueRefillEngine.cs b/OpenSim/Region/ClientStack/Linden/UDP/OutgoingQueueRefillEngine.cs
deleted file mode 100644
index 1e915c3..0000000
--- a/OpenSim/Region/ClientStack/Linden/UDP/OutgoingQueueRefillEngine.cs
+++ /dev/null
@@ -1,288 +0,0 @@
1/*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSimulator Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28using System;
29using System.Collections.Concurrent;
30using System.Reflection;
31using System.Threading;
32using log4net;
33using OpenSim.Framework;
34using OpenSim.Framework.Monitoring;
35using OpenSim.Region.Framework.Scenes;
36
37namespace OpenSim.Region.ClientStack.LindenUDP
38{
39 public struct RefillRequest
40 {
41 public LLUDPClient Client;
42 public ThrottleOutPacketTypeFlags Categories;
43
44 public RefillRequest(LLUDPClient client, ThrottleOutPacketTypeFlags categories)
45 {
46 Client = client;
47 Categories = categories;
48 }
49 }
50
51 public class OutgoingQueueRefillEngine
52 {
53 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
54
55 public bool IsRunning { get; private set; }
56
57 /// <summary>
58 /// The timeout in milliseconds to wait for at least one event to be written when the recorder is stopping.
59 /// </summary>
60 public int RequestProcessTimeoutOnStop { get; set; }
61
62 /// <summary>
63 /// Controls whether we need to warn in the log about exceeding the max queue size.
64 /// </summary>
65 /// <remarks>
66 /// This is flipped to false once queue max has been exceeded and back to true when it falls below max, in
67 /// order to avoid spamming the log with lots of warnings.
68 /// </remarks>
69 private bool m_warnOverMaxQueue = true;
70
71 private BlockingCollection<RefillRequest> m_requestQueue;
72
73 private CancellationTokenSource m_cancelSource = new CancellationTokenSource();
74
75 private LLUDPServer m_udpServer;
76
77 private Stat m_oqreRequestsWaitingStat;
78
79 /// <summary>
80 /// Used to signal that we are ready to complete stop.
81 /// </summary>
82 private ManualResetEvent m_finishedProcessingAfterStop = new ManualResetEvent(false);
83
84 public OutgoingQueueRefillEngine(LLUDPServer server)
85 {
86 RequestProcessTimeoutOnStop = 5000;
87 m_udpServer = server;
88
89 MainConsole.Instance.Commands.AddCommand(
90 "Debug",
91 false,
92 "debug lludp oqre",
93 "debug lludp oqre <start|stop|status>",
94 "Start, stop or get status of OutgoingQueueRefillEngine.",
95 "If stopped then refill requests are processed directly via the threadpool.",
96 HandleOqreCommand);
97 }
98
99 public void Start()
100 {
101 lock (this)
102 {
103 if (IsRunning)
104 return;
105
106 IsRunning = true;
107
108 m_finishedProcessingAfterStop.Reset();
109
110 m_requestQueue = new BlockingCollection<RefillRequest>(new ConcurrentQueue<RefillRequest>(), 5000);
111
112 m_oqreRequestsWaitingStat =
113 new Stat(
114 "OQRERequestsWaiting",
115 "Number of outgong queue refill requests waiting for processing.",
116 "",
117 "",
118 "clientstack",
119 m_udpServer.Scene.Name,
120 StatType.Pull,
121 MeasuresOfInterest.None,
122 stat => stat.Value = m_requestQueue.Count,
123 StatVerbosity.Debug);
124
125 StatsManager.RegisterStat(m_oqreRequestsWaitingStat);
126
127 WorkManager.StartThread(
128 ProcessRequests,
129 String.Format("OutgoingQueueRefillEngineThread ({0})", m_udpServer.Scene.Name),
130 ThreadPriority.Normal,
131 false,
132 true,
133 null,
134 int.MaxValue);
135 }
136 }
137
138 public void Stop()
139 {
140 lock (this)
141 {
142 try
143 {
144 if (!IsRunning)
145 return;
146
147 IsRunning = false;
148
149 int requestsLeft = m_requestQueue.Count;
150
151 if (requestsLeft <= 0)
152 {
153 m_cancelSource.Cancel();
154 }
155 else
156 {
157 m_log.InfoFormat("[OUTGOING QUEUE REFILL ENGINE]: Waiting to write {0} events after stop.", requestsLeft);
158
159 while (requestsLeft > 0)
160 {
161 if (!m_finishedProcessingAfterStop.WaitOne(RequestProcessTimeoutOnStop))
162 {
163 // After timeout no events have been written
164 if (requestsLeft == m_requestQueue.Count)
165 {
166 m_log.WarnFormat(
167 "[OUTGOING QUEUE REFILL ENGINE]: No requests processed after {0} ms wait. Discarding remaining {1} requests",
168 RequestProcessTimeoutOnStop, requestsLeft);
169
170 break;
171 }
172 }
173
174 requestsLeft = m_requestQueue.Count;
175 }
176 }
177 }
178 finally
179 {
180 m_cancelSource.Dispose();
181 StatsManager.DeregisterStat(m_oqreRequestsWaitingStat);
182 m_oqreRequestsWaitingStat = null;
183 m_requestQueue = null;
184 }
185 }
186 }
187
188 public bool QueueRequest(LLUDPClient client, ThrottleOutPacketTypeFlags categories)
189 {
190 if (m_requestQueue.Count < m_requestQueue.BoundedCapacity)
191 {
192// m_log.DebugFormat(
193// "[OUTGOING QUEUE REFILL ENGINE]: Adding request for categories {0} for {1} in {2}",
194// categories, client.AgentID, m_udpServer.Scene.Name);
195
196 m_requestQueue.Add(new RefillRequest(client, categories));
197
198 if (!m_warnOverMaxQueue)
199 m_warnOverMaxQueue = true;
200
201 return true;
202 }
203 else
204 {
205 if (m_warnOverMaxQueue)
206 {
207 m_log.WarnFormat(
208 "[OUTGOING QUEUE REFILL ENGINE]: Request queue at maximum capacity, not recording request from {0} in {1}",
209 client.AgentID, m_udpServer.Scene.Name);
210
211 m_warnOverMaxQueue = false;
212 }
213
214 return false;
215 }
216 }
217
218 private void ProcessRequests()
219 {
220 Thread.CurrentThread.Priority = ThreadPriority.Highest;
221
222 try
223 {
224 while (IsRunning || m_requestQueue.Count > 0)
225 {
226 RefillRequest req = m_requestQueue.Take(m_cancelSource.Token);
227
228 // QueueEmpty callback = req.Client.OnQueueEmpty;
229 //
230 // if (callback != null)
231 // {
232 // try
233 // {
234 // callback(req.Categories);
235 // }
236 // catch (Exception e)
237 // {
238 // m_log.Error("[OUTGOING QUEUE REFILL ENGINE]: ProcessRequests(" + req.Categories + ") threw an exception: " + e.Message, e);
239 // }
240 // }
241
242 req.Client.FireQueueEmpty(req.Categories);
243 }
244 }
245 catch (OperationCanceledException)
246 {
247 }
248
249 m_finishedProcessingAfterStop.Set();
250 }
251
252 private void HandleOqreCommand(string module, string[] args)
253 {
254 if (SceneManager.Instance.CurrentScene != null && SceneManager.Instance.CurrentScene != m_udpServer.Scene)
255 return;
256
257 if (args.Length != 4)
258 {
259 MainConsole.Instance.Output("Usage: debug lludp oqre <stop|start|status>");
260 return;
261 }
262
263 string subCommand = args[3];
264
265 if (subCommand == "stop")
266 {
267 Stop();
268 MainConsole.Instance.OutputFormat("Stopped OQRE for {0}", m_udpServer.Scene.Name);
269 }
270 else if (subCommand == "start")
271 {
272 Start();
273 MainConsole.Instance.OutputFormat("Started OQRE for {0}", m_udpServer.Scene.Name);
274 }
275 else if (subCommand == "status")
276 {
277 MainConsole.Instance.OutputFormat("OQRE in {0}", m_udpServer.Scene.Name);
278 MainConsole.Instance.OutputFormat("Running: {0}", IsRunning);
279 MainConsole.Instance.OutputFormat(
280 "Requests waiting: {0}", IsRunning ? m_requestQueue.Count.ToString() : "n/a");
281 }
282 else
283 {
284 MainConsole.Instance.OutputFormat("Unrecognized OQRE subcommand {0}", subCommand);
285 }
286 }
287 }
288} \ No newline at end of file