aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Framework
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--OpenSim/Framework/ClientManager.cs274
-rw-r--r--OpenSim/Framework/Communications/GenericAsyncResult.cs1
-rw-r--r--OpenSim/Framework/Communications/RestClient.cs6
-rw-r--r--OpenSim/Framework/IClientAPI.cs4
-rw-r--r--OpenSim/Framework/IScene.cs1
-rw-r--r--OpenSim/Framework/LocklessQueue.cs130
-rw-r--r--OpenSim/Framework/Parallel.cs3
-rw-r--r--OpenSim/Framework/ThrottleOutPacketType.cs13
8 files changed, 290 insertions, 142 deletions
diff --git a/OpenSim/Framework/ClientManager.cs b/OpenSim/Framework/ClientManager.cs
index 094a3ff..61b59e7 100644
--- a/OpenSim/Framework/ClientManager.cs
+++ b/OpenSim/Framework/ClientManager.cs
@@ -28,193 +28,195 @@
28using System; 28using System;
29using System.Collections.Generic; 29using System.Collections.Generic;
30using System.Reflection; 30using System.Reflection;
31using log4net; 31using System.Net;
32using OpenMetaverse; 32using OpenMetaverse;
33using OpenMetaverse.Packets; 33using OpenMetaverse.Packets;
34 34
35namespace OpenSim.Framework 35namespace OpenSim.Framework
36{ 36{
37 public delegate void ForEachClientDelegate(IClientAPI client); 37 /// <summary>
38 38 /// Maps from client AgentID and RemoteEndPoint values to IClientAPI
39 /// references for all of the connected clients
40 /// </summary>
39 public class ClientManager 41 public class ClientManager
40 { 42 {
41 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); 43 /// <summary>A dictionary mapping from <seealso cref="UUID"/>
42 44 /// to <seealso cref="IClientAPI"/> references</summary>
43 private Dictionary<uint, IClientAPI> m_clients; 45 private Dictionary<UUID, IClientAPI> m_dict1;
46 /// <summary>A dictionary mapping from <seealso cref="IPEndPoint"/>
47 /// to <seealso cref="IClientAPI"/> references</summary>
48 private Dictionary<IPEndPoint, IClientAPI> m_dict2;
49 /// <summary>An immutable collection of <seealso cref="IClientAPI"/>
50 /// references</summary>
51 private IClientAPI[] m_array;
52 /// <summary>Synchronization object for writing to the collections</summary>
53 private object m_syncRoot = new object();
54
55 /// <summary>Number of clients in the collection</summary>
56 public int Count { get { return m_dict1.Count; } }
44 57
58 /// <summary>
59 /// Default constructor
60 /// </summary>
45 public ClientManager() 61 public ClientManager()
46 { 62 {
47 m_clients = new Dictionary<uint, IClientAPI>(); 63 m_dict1 = new Dictionary<UUID, IClientAPI>();
64 m_dict2 = new Dictionary<IPEndPoint, IClientAPI>();
65 m_array = new IClientAPI[0];
48 } 66 }
49 67
50 public void ForEachClient(ForEachClientDelegate whatToDo) 68 /// <summary>
69 /// Add a client reference to the collection if it does not already
70 /// exist
71 /// </summary>
72 /// <param name="value">Reference to the client object</param>
73 /// <returns>True if the client reference was successfully added,
74 /// otherwise false if the given key already existed in the collection</returns>
75 public bool Add(IClientAPI value)
51 { 76 {
52 IClientAPI[] LocalClients; 77 lock (m_syncRoot)
53 lock (m_clients)
54 { 78 {
55 LocalClients = new IClientAPI[m_clients.Count]; 79 if (m_dict1.ContainsKey(value.AgentId) || m_dict2.ContainsKey(value.RemoteEndPoint))
56 m_clients.Values.CopyTo(LocalClients, 0); 80 return false;
57 }
58 81
59 for (int i = 0; i < LocalClients.Length; i++) 82 m_dict1[value.AgentId] = value;
60 { 83 m_dict2[value.RemoteEndPoint] = value;
61 try
62 {
63 whatToDo(LocalClients[i]);
64 }
65 catch (Exception e)
66 {
67 m_log.Warn("[CLIENT]: Unable to do ForEachClient for one of the clients" + "\n Reason: " + e.ToString());
68 }
69 }
70 }
71 84
72 public void Remove(uint id) 85 IClientAPI[] oldArray = m_array;
73 { 86 int oldLength = oldArray.Length;
74 lock (m_clients)
75 {
76 m_clients.Remove(id);
77 }
78 }
79 87
80 public void Add(uint id, IClientAPI client) 88 IClientAPI[] newArray = new IClientAPI[oldLength + 1];
81 { 89 for (int i = 0; i < oldLength; i++)
82 lock (m_clients) 90 newArray[i] = oldArray[i];
83 { 91 newArray[oldLength] = value;
84 m_clients.Add(id, client); 92
93 m_array = newArray;
85 } 94 }
95
96 return true;
86 } 97 }
87 98
88 /// <summary> 99 /// <summary>
89 /// Pass incoming packet to client. 100 /// Remove a client from the collection
90 /// </summary> 101 /// </summary>
91 /// <param name="circuitCode">uint identifying the connection/client.</param> 102 /// <param name="key">UUID of the client to remove</param>
92 /// <param name="packet">object containing the packet.</param> 103 /// <returns>True if a client was removed, or false if the given UUID
93 public void InPacket(uint circuitCode, object packet) 104 /// was not present in the collection</returns>
94 { 105 public bool Remove(UUID key)
95 IClientAPI client;
96 bool tryGetRet = false;
97
98 lock (m_clients)
99 tryGetRet = m_clients.TryGetValue(circuitCode, out client);
100
101 if (tryGetRet)
102 {
103 client.InPacket(packet);
104 }
105 }
106
107 public void CloseAllAgents(uint circuitCode)
108 { 106 {
109 IClientAPI client; 107 lock (m_syncRoot)
110 bool tryGetRet = false;
111 lock (m_clients)
112 tryGetRet = m_clients.TryGetValue(circuitCode, out client);
113 if (tryGetRet)
114 { 108 {
115 CloseAllCircuits(client.AgentId); 109 IClientAPI value;
116 } 110 if (m_dict1.TryGetValue(key, out value))
117 } 111 {
112 m_dict1.Remove(key);
113 m_dict2.Remove(value.RemoteEndPoint);
118 114
119 public void CloseAllCircuits(UUID agentId) 115 IClientAPI[] oldArray = m_array;
120 { 116 int oldLength = oldArray.Length;
121 uint[] circuits = GetAllCircuits(agentId);
122 // We're using a for loop here so changes to the circuits don't cause it to completely fail.
123 117
124 for (int i = 0; i < circuits.Length; i++) 118 IClientAPI[] newArray = new IClientAPI[oldLength - 1];
125 { 119 int j = 0;
126 IClientAPI client; 120 for (int i = 0; i < oldLength; i++)
127 try
128 {
129 bool tryGetRet = false;
130 lock (m_clients)
131 tryGetRet = m_clients.TryGetValue(circuits[i], out client);
132 if (tryGetRet)
133 { 121 {
134 Remove(client.CircuitCode); 122 if (oldArray[i] != value)
135 client.Close(false); 123 newArray[j++] = oldArray[i];
136 } 124 }
137 } 125
138 catch (Exception e) 126 m_array = newArray;
139 { 127 return true;
140 m_log.Error(string.Format("[CLIENT]: Unable to shutdown circuit for: {0}\n Reason: {1}", agentId, e));
141 } 128 }
142 } 129 }
130
131 return false;
143 } 132 }
144 133
145 // [Obsolete("Using Obsolete to drive development is invalid. Obsolete presumes that something new has already been created to replace this.")] 134 /// <summary>
146 public uint[] GetAllCircuits(UUID agentId) 135 /// Resets the client collection
136 /// </summary>
137 public void Clear()
147 { 138 {
148 List<uint> circuits = new List<uint>(); 139 lock (m_syncRoot)
149 // Wasteful, I know
150 IClientAPI[] LocalClients = new IClientAPI[0];
151 lock (m_clients)
152 { 140 {
153 LocalClients = new IClientAPI[m_clients.Count]; 141 m_dict1.Clear();
154 m_clients.Values.CopyTo(LocalClients, 0); 142 m_dict2.Clear();
143 m_array = new IClientAPI[0];
155 } 144 }
156
157 for (int i = 0; i < LocalClients.Length; i++)
158 {
159 if (LocalClients[i].AgentId == agentId)
160 {
161 circuits.Add(LocalClients[i].CircuitCode);
162 }
163 }
164 return circuits.ToArray();
165 } 145 }
166 146
167 public List<uint> GetAllCircuitCodes() 147 /// <summary>
148 /// Checks if a UUID is in the collection
149 /// </summary>
150 /// <param name="key">UUID to check for</param>
151 /// <returns>True if the UUID was found in the collection, otherwise false</returns>
152 public bool ContainsKey(UUID key)
168 { 153 {
169 List<uint> circuits; 154 return m_dict1.ContainsKey(key);
170 155 }
171 lock (m_clients)
172 {
173 circuits = new List<uint>(m_clients.Keys);
174 }
175 156
176 return circuits; 157 /// <summary>
158 /// Checks if an endpoint is in the collection
159 /// </summary>
160 /// <param name="key">Endpoint to check for</param>
161 /// <returns>True if the endpoint was found in the collection, otherwise false</returns>
162 public bool ContainsKey(IPEndPoint key)
163 {
164 return m_dict2.ContainsKey(key);
177 } 165 }
178 166
179 public void ViewerEffectHandler(IClientAPI sender, List<ViewerEffectEventHandlerArg> args) 167 /// <summary>
168 /// Attempts to fetch a value out of the collection
169 /// </summary>
170 /// <param name="key">UUID of the client to retrieve</param>
171 /// <param name="value">Retrieved client, or null on lookup failure</param>
172 /// <returns>True if the lookup succeeded, otherwise false</returns>
173 public bool TryGetValue(UUID key, out IClientAPI value)
180 { 174 {
181 // TODO: don't create new blocks if recycling an old packet 175 try { return m_dict1.TryGetValue(key, out value); }
182 List<ViewerEffectPacket.EffectBlock> effectBlock = new List<ViewerEffectPacket.EffectBlock>(); 176 catch (Exception)
183 for (int i = 0; i < args.Count; i++)
184 { 177 {
185 ViewerEffectPacket.EffectBlock effect = new ViewerEffectPacket.EffectBlock(); 178 value = null;
186 effect.AgentID = args[i].AgentID; 179 return false;
187 effect.Color = args[i].Color;
188 effect.Duration = args[i].Duration;
189 effect.ID = args[i].ID;
190 effect.Type = args[i].Type;
191 effect.TypeData = args[i].TypeData;
192 effectBlock.Add(effect);
193 } 180 }
194 ViewerEffectPacket.EffectBlock[] effectBlockArray = effectBlock.ToArray(); 181 }
195 182
196 IClientAPI[] LocalClients; 183 /// <summary>
197 lock (m_clients) 184 /// Attempts to fetch a value out of the collection
185 /// </summary>
186 /// <param name="key">Endpoint of the client to retrieve</param>
187 /// <param name="value">Retrieved client, or null on lookup failure</param>
188 /// <returns>True if the lookup succeeded, otherwise false</returns>
189 public bool TryGetValue(IPEndPoint key, out IClientAPI value)
190 {
191 try { return m_dict2.TryGetValue(key, out value); }
192 catch (Exception)
198 { 193 {
199 LocalClients = new IClientAPI[m_clients.Count]; 194 value = null;
200 m_clients.Values.CopyTo(LocalClients, 0); 195 return false;
201 } 196 }
197 }
202 198
203 for (int i = 0; i < LocalClients.Length; i++) 199 /// <summary>
204 { 200 /// Performs a given task in parallel for each of the elements in the
205 if (LocalClients[i].AgentId != sender.AgentId) 201 /// collection
206 { 202 /// </summary>
207 LocalClients[i].SendViewerEffect(effectBlockArray); 203 /// <param name="action">Action to perform on each element</param>
208 } 204 public void ForEach(Action<IClientAPI> action)
209 } 205 {
206 IClientAPI[] localArray = m_array;
207 Parallel.ForEach<IClientAPI>(localArray, action);
210 } 208 }
211 209
212 public bool TryGetClient(uint circuitId, out IClientAPI user) 210 /// <summary>
211 /// Performs a given task synchronously for each of the elements in
212 /// the collection
213 /// </summary>
214 /// <param name="action">Action to perform on each element</param>
215 public void ForEachSync(Action<IClientAPI> action)
213 { 216 {
214 lock (m_clients) 217 IClientAPI[] localArray = m_array;
215 { 218 for (int i = 0; i < localArray.Length; i++)
216 return m_clients.TryGetValue(circuitId, out user); 219 action(localArray[i]);
217 }
218 } 220 }
219 } 221 }
220} 222}
diff --git a/OpenSim/Framework/Communications/GenericAsyncResult.cs b/OpenSim/Framework/Communications/GenericAsyncResult.cs
index efd2f43..8e3f62b 100644
--- a/OpenSim/Framework/Communications/GenericAsyncResult.cs
+++ b/OpenSim/Framework/Communications/GenericAsyncResult.cs
@@ -146,6 +146,7 @@ namespace OpenSim.Framework.Communications
146 // If the operation isn't done, wait for it 146 // If the operation isn't done, wait for it
147 AsyncWaitHandle.WaitOne(); 147 AsyncWaitHandle.WaitOne();
148 AsyncWaitHandle.Close(); 148 AsyncWaitHandle.Close();
149 m_waitHandle.Close();
149 m_waitHandle = null; // Allow early GC 150 m_waitHandle = null; // Allow early GC
150 } 151 }
151 152
diff --git a/OpenSim/Framework/Communications/RestClient.cs b/OpenSim/Framework/Communications/RestClient.cs
index d98f47d..a74169e 100644
--- a/OpenSim/Framework/Communications/RestClient.cs
+++ b/OpenSim/Framework/Communications/RestClient.cs
@@ -105,7 +105,7 @@ namespace OpenSim.Framework.Communications
105 /// <summary> 105 /// <summary>
106 /// This flag will help block the main synchroneous method, in case we run in synchroneous mode 106 /// This flag will help block the main synchroneous method, in case we run in synchroneous mode
107 /// </summary> 107 /// </summary>
108 public static ManualResetEvent _allDone = new ManualResetEvent(false); 108 //public static ManualResetEvent _allDone = new ManualResetEvent(false);
109 109
110 /// <summary> 110 /// <summary>
111 /// Default time out period 111 /// Default time out period
@@ -282,12 +282,12 @@ namespace OpenSim.Framework.Communications
282 else 282 else
283 { 283 {
284 s.Close(); 284 s.Close();
285 _allDone.Set(); 285 //_allDone.Set();
286 } 286 }
287 } 287 }
288 catch (Exception e) 288 catch (Exception e)
289 { 289 {
290 _allDone.Set(); 290 //_allDone.Set();
291 _asyncException = e; 291 _asyncException = e;
292 } 292 }
293 } 293 }
diff --git a/OpenSim/Framework/IClientAPI.cs b/OpenSim/Framework/IClientAPI.cs
index d3bd9e7..29846f5 100644
--- a/OpenSim/Framework/IClientAPI.cs
+++ b/OpenSim/Framework/IClientAPI.cs
@@ -561,6 +561,8 @@ namespace OpenSim.Framework
561 // [Obsolete("LLClientView Specific - Circuits are unique to LLClientView")] 561 // [Obsolete("LLClientView Specific - Circuits are unique to LLClientView")]
562 uint CircuitCode { get; } 562 uint CircuitCode { get; }
563 563
564 IPEndPoint RemoteEndPoint { get; }
565
564 event GenericMessage OnGenericMessage; 566 event GenericMessage OnGenericMessage;
565 567
566 // [Obsolete("LLClientView Specific - Replace with more bare-bones arguments.")] 568 // [Obsolete("LLClientView Specific - Replace with more bare-bones arguments.")]
@@ -802,7 +804,7 @@ namespace OpenSim.Framework
802 804
803 void InPacket(object NewPack); 805 void InPacket(object NewPack);
804 void ProcessInPacket(Packet NewPack); 806 void ProcessInPacket(Packet NewPack);
805 void Close(bool ShutdownCircuit); 807 void Close();
806 void Kick(string message); 808 void Kick(string message);
807 809
808 /// <summary> 810 /// <summary>
diff --git a/OpenSim/Framework/IScene.cs b/OpenSim/Framework/IScene.cs
index 489653f..f34027d 100644
--- a/OpenSim/Framework/IScene.cs
+++ b/OpenSim/Framework/IScene.cs
@@ -71,7 +71,6 @@ namespace OpenSim.Framework
71 71
72 void AddNewClient(IClientAPI client); 72 void AddNewClient(IClientAPI client);
73 void RemoveClient(UUID agentID); 73 void RemoveClient(UUID agentID);
74 void CloseAllAgents(uint circuitcode);
75 74
76 void Restart(int seconds); 75 void Restart(int seconds);
77 //RegionInfo OtherRegionUp(RegionInfo thisRegion); 76 //RegionInfo OtherRegionUp(RegionInfo thisRegion);
diff --git a/OpenSim/Framework/LocklessQueue.cs b/OpenSim/Framework/LocklessQueue.cs
new file mode 100644
index 0000000..dd3d201
--- /dev/null
+++ b/OpenSim/Framework/LocklessQueue.cs
@@ -0,0 +1,130 @@
1/*
2 * Copyright (c) 2009, openmetaverse.org
3 * All rights reserved.
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 *
8 * - Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 * - Neither the name of the openmetaverse.org nor the names
11 * of its contributors may be used to endorse or promote products derived from
12 * this software without specific prior written permission.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
18 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 * POSSIBILITY OF SUCH DAMAGE.
25 */
26
27using System;
28using System.Threading;
29
30namespace OpenSim.Framework
31{
32 public sealed class LocklessQueue<T>
33 {
34 private sealed class SingleLinkNode
35 {
36 public SingleLinkNode Next;
37 public T Item;
38 }
39
40 SingleLinkNode head;
41 SingleLinkNode tail;
42 int count;
43
44 public int Count { get { return count; } }
45
46 public LocklessQueue()
47 {
48 Init();
49 }
50
51 public void Enqueue(T item)
52 {
53 SingleLinkNode oldTail = null;
54 SingleLinkNode oldTailNext;
55
56 SingleLinkNode newNode = new SingleLinkNode();
57 newNode.Item = item;
58
59 bool newNodeWasAdded = false;
60
61 while (!newNodeWasAdded)
62 {
63 oldTail = tail;
64 oldTailNext = oldTail.Next;
65
66 if (tail == oldTail)
67 {
68 if (oldTailNext == null)
69 newNodeWasAdded = CAS(ref tail.Next, null, newNode);
70 else
71 CAS(ref tail, oldTail, oldTailNext);
72 }
73 }
74
75 CAS(ref tail, oldTail, newNode);
76 Interlocked.Increment(ref count);
77 }
78
79 public bool Dequeue(out T item)
80 {
81 item = default(T);
82 SingleLinkNode oldHead = null;
83 bool haveAdvancedHead = false;
84
85 while (!haveAdvancedHead)
86 {
87 oldHead = head;
88 SingleLinkNode oldTail = tail;
89 SingleLinkNode oldHeadNext = oldHead.Next;
90
91 if (oldHead == head)
92 {
93 if (oldHead == oldTail)
94 {
95 if (oldHeadNext == null)
96 return false;
97
98 CAS(ref tail, oldTail, oldHeadNext);
99 }
100 else
101 {
102 item = oldHeadNext.Item;
103 haveAdvancedHead = CAS(ref head, oldHead, oldHeadNext);
104 }
105 }
106 }
107
108 Interlocked.Decrement(ref count);
109 return true;
110 }
111
112 public void Clear()
113 {
114 Init();
115 }
116
117 private void Init()
118 {
119 count = 0;
120 head = tail = new SingleLinkNode();
121 }
122
123 private static bool CAS(ref SingleLinkNode location, SingleLinkNode comparand, SingleLinkNode newValue)
124 {
125 return
126 (object)comparand ==
127 (object)Interlocked.CompareExchange<SingleLinkNode>(ref location, newValue, comparand);
128 }
129 }
130} \ No newline at end of file
diff --git a/OpenSim/Framework/Parallel.cs b/OpenSim/Framework/Parallel.cs
index 74537ba..6efdad0 100644
--- a/OpenSim/Framework/Parallel.cs
+++ b/OpenSim/Framework/Parallel.cs
@@ -89,6 +89,7 @@ namespace OpenSim.Framework
89 } 89 }
90 90
91 threadFinishEvent.WaitOne(); 91 threadFinishEvent.WaitOne();
92 threadFinishEvent.Close();
92 93
93 if (exception != null) 94 if (exception != null)
94 throw new Exception(exception.Message, exception); 95 throw new Exception(exception.Message, exception);
@@ -148,6 +149,7 @@ namespace OpenSim.Framework
148 } 149 }
149 150
150 threadFinishEvent.WaitOne(); 151 threadFinishEvent.WaitOne();
152 threadFinishEvent.Close();
151 153
152 if (exception != null) 154 if (exception != null)
153 throw new Exception(exception.Message, exception); 155 throw new Exception(exception.Message, exception);
@@ -199,6 +201,7 @@ namespace OpenSim.Framework
199 } 201 }
200 202
201 threadFinishEvent.WaitOne(); 203 threadFinishEvent.WaitOne();
204 threadFinishEvent.Close();
202 205
203 if (exception != null) 206 if (exception != null)
204 throw new Exception(exception.Message, exception); 207 throw new Exception(exception.Message, exception);
diff --git a/OpenSim/Framework/ThrottleOutPacketType.cs b/OpenSim/Framework/ThrottleOutPacketType.cs
index fd490a5..e21ff32 100644
--- a/OpenSim/Framework/ThrottleOutPacketType.cs
+++ b/OpenSim/Framework/ThrottleOutPacketType.cs
@@ -31,13 +31,24 @@ namespace OpenSim.Framework
31{ 31{
32 public enum ThrottleOutPacketType : int 32 public enum ThrottleOutPacketType : int
33 { 33 {
34 Unknown = -1, // Also doubles as 'do not throttle' 34 /// <summary>Unthrottled packets</summary>
35 Unknown = -1,
36 /// <summary>Packets that are being resent</summary>
35 Resend = 0, 37 Resend = 0,
38 /// <summary>Terrain data</summary>
36 Land = 1, 39 Land = 1,
40 /// <summary>Wind data</summary>
37 Wind = 2, 41 Wind = 2,
42 /// <summary>Cloud data</summary>
38 Cloud = 3, 43 Cloud = 3,
44 /// <summary>Any packets that do not fit into the other throttles</summary>
39 Task = 4, 45 Task = 4,
46 /// <summary>Texture assets</summary>
40 Texture = 5, 47 Texture = 5,
48 /// <summary>Non-texture assets</summary>
41 Asset = 6, 49 Asset = 6,
50 /// <summary>Avatar and primitive data</summary>
51 /// <remarks>This is a sub-category of Task</remarks>
52 State = 7,
42 } 53 }
43} 54}