diff options
14 files changed, 88 insertions, 219 deletions
diff --git a/src/System.Json/JsonValue.cs b/src/System.Json/JsonValue.cs index 7c41d677..f4d0b3fc 100644 --- a/src/System.Json/JsonValue.cs +++ b/src/System.Json/JsonValue.cs @@ -21,10 +21,7 @@ namespace System.Json [DataContract] public class JsonValue : IEnumerable<KeyValuePair<string, JsonValue>>, IDynamicMetaObjectProvider { - private static object lockKey = new object(); - - // Double-checked locking pattern requires volatile for read/write synchronization - private static volatile JsonValue defaultInstance; + private static JsonValue defaultInstance = new JsonValue(); private int changingListenersCount; private int changedListenersCount; @@ -130,21 +127,7 @@ namespace System.Json /// </summary> private static JsonValue DefaultInstance { - get - { - if (defaultInstance == null) - { - lock (lockKey) - { - if (defaultInstance == null) - { - defaultInstance = new JsonValue(); - } - } - } - - return defaultInstance; - } + get { return defaultInstance; } } /// <summary> diff --git a/src/System.Web.Http.SelfHost/Channels/HttpMessageEncoderFactory.cs b/src/System.Web.Http.SelfHost/Channels/HttpMessageEncoderFactory.cs index 15c0354d..75627968 100644 --- a/src/System.Web.Http.SelfHost/Channels/HttpMessageEncoderFactory.cs +++ b/src/System.Web.Http.SelfHost/Channels/HttpMessageEncoderFactory.cs @@ -4,6 +4,7 @@ using System.IO; using System.Net.Http; using System.ServiceModel; using System.ServiceModel.Channels; +using System.Threading; using System.Threading.Tasks; using System.Web.Http.SelfHost.Properties; using System.Web.Http.SelfHost.ServiceModel.Channels; @@ -230,10 +231,8 @@ namespace System.Web.Http.SelfHost.Channels private class ByteArrayBufferManagerContent : ByteArrayContent { - private bool _disposed; private BufferManager _bufferManager; private byte[] _content; - private object _disposingLock; public ByteArrayBufferManagerContent(BufferManager bufferManager, byte[] content, int offset, int count) : base(content, offset, count) @@ -242,23 +241,19 @@ namespace System.Web.Http.SelfHost.Channels _bufferManager = bufferManager; _content = content; - _disposingLock = new object(); } protected override void Dispose(bool disposing) { try { - if (disposing && !_disposed) + if (disposing) { - lock (_disposingLock) + BufferManager oldBufferManager = Interlocked.Exchange(ref _bufferManager, null); + if (oldBufferManager != null) { - if (!_disposed) - { - _disposed = true; - _bufferManager.ReturnBuffer(_content); - _content = null; - } + oldBufferManager.ReturnBuffer(_content); + _content = null; } } } diff --git a/src/System.Web.Http.SelfHost/Channels/HttpMessageEncodingRequestContext.cs b/src/System.Web.Http.SelfHost/Channels/HttpMessageEncodingRequestContext.cs index 33aaf3d4..9e9f5d14 100644 --- a/src/System.Web.Http.SelfHost/Channels/HttpMessageEncodingRequestContext.cs +++ b/src/System.Web.Http.SelfHost/Channels/HttpMessageEncodingRequestContext.cs @@ -17,14 +17,11 @@ namespace System.Web.Http.SelfHost.Channels private RequestContext _innerContext; private Message _configuredRequestMessage; - private bool _isRequestConfigured; - private object _requestConfigurationLock; public HttpMessageEncodingRequestContext(RequestContext innerContext) { Contract.Assert(innerContext != null, "The 'innerContext' parameter should not be null."); _innerContext = innerContext; - _requestConfigurationLock = new object(); } internal Exception Exception { get; set; } @@ -35,17 +32,9 @@ namespace System.Web.Http.SelfHost.Channels { get { - if (!_isRequestConfigured) + if (_configuredRequestMessage == null) { - lock (_requestConfigurationLock) - { - if (!_isRequestConfigured) - { - Message innerMessage = _innerContext.RequestMessage; - _configuredRequestMessage = ConfigureRequestMessage(innerMessage); - _isRequestConfigured = true; - } - } + _configuredRequestMessage = ConfigureRequestMessage(_innerContext.RequestMessage); } return _configuredRequestMessage; diff --git a/src/System.Web.Http.SelfHost/ServiceModel/Activation/AspNetEnvironment.cs b/src/System.Web.Http.SelfHost/ServiceModel/Activation/AspNetEnvironment.cs deleted file mode 100644 index 14f4f24d..00000000 --- a/src/System.Web.Http.SelfHost/ServiceModel/Activation/AspNetEnvironment.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Configuration; -using System.Diagnostics.CodeAnalysis; -using System.ServiceModel.Channels; - -namespace System.Web.Http.SelfHost.ServiceModel.Activation -{ - internal class AspNetEnvironment - { - public const string HostingMessagePropertyName = "webhost"; - - private const string HostingMessagePropertyTypeName = "System.ServiceModel.Activation.HostingMessageProperty"; - private static AspNetEnvironment _current; - private static readonly object _thisLock = new object(); - - public static AspNetEnvironment Current - { - get - { - if (_current == null) - { - lock (_thisLock) - { - if (_current == null) - { - _current = new AspNetEnvironment(); - } - } - } - - return _current; - } - } - - // ALTERED_FOR_PORT: - // The GetHostingProperty() code below is an altered implementation from the System.ServiceModel.Activation.HostedAspNetEnvironment class. - // The original implementation casts the hostingProperty to type System.ServiceModel.Activation.HostingMessageProperty. However, - // this class is internal sealed, therefore we simply check the type name. - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This is existing public API")] - public object GetHostingProperty(Message message) - { - object hostingProperty; - if (message.Properties.TryGetValue(HostingMessagePropertyName, out hostingProperty)) - { - string hostingPropertyName = hostingProperty.GetType().FullName; - if (String.Equals(hostingPropertyName, HostingMessagePropertyTypeName, System.StringComparison.Ordinal)) - { - return hostingProperty; - } - } - - return null; - } - - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This is existing public API")] - public object GetConfigurationSection(string sectionPath) - { - return ConfigurationManager.GetSection(sectionPath); - } - } -} diff --git a/src/System.Web.Http.SelfHost/ServiceModel/Channels/AsyncResult.cs b/src/System.Web.Http.SelfHost/ServiceModel/Channels/AsyncResult.cs index cbf0ce88..ba1c07c3 100644 --- a/src/System.Web.Http.SelfHost/ServiceModel/Channels/AsyncResult.cs +++ b/src/System.Web.Http.SelfHost/ServiceModel/Channels/AsyncResult.cs @@ -11,25 +11,22 @@ namespace System.Web.Http.SelfHost.ServiceModel.Channels internal abstract class AsyncResult : IAsyncResult { private static AsyncCallback _asyncCompletionWrapperCallback; - private AsyncCallback _completionCallback; + private readonly AsyncCallback _completionCallback; private bool _completedSynchronously; private bool _endCalled; private Exception _exception; private bool _isCompleted; private AsyncCompletion _nextAsyncCompletion; - private object _state; + private readonly object _state; private Action _beforePrepareAsyncCompletionAction; private Func<IAsyncResult, bool> _checkSyncValidationFunc; - private ManualResetEvent _manualResetEvent; - - private readonly object _thisLock; + private readonly object _manualResetEventLock = new object(); protected AsyncResult(AsyncCallback callback, object state) { _completionCallback = callback; _state = state; - _thisLock = new object(); } /// <summary> @@ -49,12 +46,7 @@ namespace System.Web.Http.SelfHost.ServiceModel.Channels { get { - if (_manualResetEvent != null) - { - return _manualResetEvent; - } - - lock (ThisLock) + lock (_manualResetEventLock) { if (_manualResetEvent == null) { @@ -84,11 +76,6 @@ namespace System.Web.Http.SelfHost.ServiceModel.Channels // used in conjunction with PrepareAsyncCompletion to allow for finally blocks protected Action<AsyncResult, Exception> OnCompleting { get; set; } - private object ThisLock - { - get { return _thisLock; } - } - // subclasses like TraceAsyncResult can use this to wrap the callback functionality in a scope protected Action<AsyncCallback, IAsyncResult> VirtualCallback { get; set; } @@ -123,7 +110,7 @@ namespace System.Web.Http.SelfHost.ServiceModel.Channels } else { - lock (ThisLock) + lock (_manualResetEventLock) { _isCompleted = true; if (_manualResetEvent != null) diff --git a/src/System.Web.Http.SelfHost/System.Web.Http.SelfHost.csproj b/src/System.Web.Http.SelfHost/System.Web.Http.SelfHost.csproj index bed689c7..36520358 100644 --- a/src/System.Web.Http.SelfHost/System.Web.Http.SelfHost.csproj +++ b/src/System.Web.Http.SelfHost/System.Web.Http.SelfHost.csproj @@ -99,7 +99,6 @@ <Compile Include="Channels\HttpBindingSecurityModeHelper.cs" /> <Compile Include="HttpSelfHostConfiguration.cs" /> <Compile Include="HttpSelfHostServer.cs" /> - <Compile Include="ServiceModel\Activation\AspNetEnvironment.cs" /> <Compile Include="ServiceModel\Channels\AsyncResult.cs" /> <Compile Include="ServiceModel\Channels\BufferedOutputStream.cs" /> <Compile Include="ServiceModel\Channels\BufferManagerOutputStream.cs" /> diff --git a/src/System.Web.Http/Controllers/HttpActionDescriptor.cs b/src/System.Web.Http/Controllers/HttpActionDescriptor.cs index 83f7c067..f65354db 100644 --- a/src/System.Web.Http/Controllers/HttpActionDescriptor.cs +++ b/src/System.Web.Http/Controllers/HttpActionDescriptor.cs @@ -17,8 +17,7 @@ namespace System.Web.Http.Controllers private readonly ConcurrentDictionary<object, object> _properties = new ConcurrentDictionary<object, object>(); private IActionResultConverter _converter; - private readonly object _thisLock = new object(); - private Collection<FilterInfo> _filterPipeline; + private readonly Lazy<Collection<FilterInfo>> _filterPipeline; private HttpConfiguration _configuration; private HttpControllerDescriptor _controllerDescriptor; @@ -28,9 +27,11 @@ namespace System.Web.Http.Controllers protected HttpActionDescriptor() { + _filterPipeline = new Lazy<Collection<FilterInfo>>(InitializeFilterPipeline); } protected HttpActionDescriptor(HttpControllerDescriptor controllerDescriptor) + : this() { if (controllerDescriptor == null) { @@ -197,26 +198,20 @@ namespace System.Web.Http.Controllers [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Filter pipeline can be built dynamically")] public virtual Collection<FilterInfo> GetFilterPipeline() { - if (_filterPipeline == null) - { - lock (_thisLock) - { - if (_filterPipeline == null) - { - IEnumerable<IFilterProvider> filterProviders = _configuration.ServiceResolver.GetFilterProviders(); + return _filterPipeline.Value; + } - IEnumerable<FilterInfo> filters = filterProviders.SelectMany(fp => fp.GetFilters(_configuration, this)).OrderBy(f => f, FilterInfoComparer.Instance); + private Collection<FilterInfo> InitializeFilterPipeline() + { + IEnumerable<IFilterProvider> filterProviders = _configuration.ServiceResolver.GetFilterProviders(); - // Need to discard duplicate filters from the end, so that most specific ones get kept (Action scope) and - // less specific ones get removed (Global) - filters = RemoveDuplicates(filters.Reverse()).Reverse(); + IEnumerable<FilterInfo> filters = filterProviders.SelectMany(fp => fp.GetFilters(_configuration, this)).OrderBy(f => f, FilterInfoComparer.Instance); - _filterPipeline = new Collection<FilterInfo>(filters.ToList()); - } - } - } + // Need to discard duplicate filters from the end, so that most specific ones get kept (Action scope) and + // less specific ones get removed (Global) + filters = RemoveDuplicates(filters.Reverse()).Reverse(); - return _filterPipeline; + return new Collection<FilterInfo>(filters.ToList()); } private static IEnumerable<FilterInfo> RemoveDuplicates(IEnumerable<FilterInfo> filters) diff --git a/src/System.Web.Http/Dispatcher/DefaultHttpControllerFactory.cs b/src/System.Web.Http/Dispatcher/DefaultHttpControllerFactory.cs index 844396d0..37ef9d4d 100644 --- a/src/System.Web.Http/Dispatcher/DefaultHttpControllerFactory.cs +++ b/src/System.Web.Http/Dispatcher/DefaultHttpControllerFactory.cs @@ -22,9 +22,7 @@ namespace System.Web.Http.Dispatcher private readonly HttpConfiguration _configuration; private readonly HttpControllerTypeCache _controllerTypeCache; - private readonly ConcurrentDictionary<string, HttpControllerDescriptor> _controllerInfoCache = new ConcurrentDictionary<string, HttpControllerDescriptor>(); - private bool _isControllerInfoCacheInitialized; - private object _controllerInfoCacheLock = new object(); + private readonly Lazy<ConcurrentDictionary<string, HttpControllerDescriptor>> _controllerInfoCache; /// <summary> /// Initializes a new instance of the <see cref="DefaultHttpControllerFactory"/> class. @@ -37,6 +35,7 @@ namespace System.Web.Http.Dispatcher throw Error.ArgumentNull("resolver"); } + _controllerInfoCache = new Lazy<ConcurrentDictionary<string, HttpControllerDescriptor>>(InitializeControllerInfoCache); _configuration = configuration; _controllerTypeCache = new HttpControllerTypeCache(_configuration); } @@ -55,7 +54,7 @@ namespace System.Web.Http.Dispatcher } HttpControllerDescriptor controllerDescriptor; - if (_controllerInfoCache.TryGetValue(controllerName, out controllerDescriptor)) + if (_controllerInfoCache.Value.TryGetValue(controllerName, out controllerDescriptor)) { // Create controller instance return CreateInstance(controllerContext, controllerDescriptor); @@ -76,7 +75,7 @@ namespace System.Web.Http.Dispatcher // Add controller descriptor to cache controllerDescriptor = new HttpControllerDescriptor(_configuration, controllerName, match); - _controllerInfoCache.TryAdd(controllerName, controllerDescriptor); + _controllerInfoCache.Value.TryAdd(controllerName, controllerDescriptor); // Create controller instance return CreateInstance(controllerContext, controllerDescriptor); @@ -100,8 +99,7 @@ namespace System.Web.Http.Dispatcher public virtual IDictionary<string, HttpControllerDescriptor> GetControllerMapping() { - EnsureControllerInfoCacheInitialized(); - return _controllerInfoCache.ToDictionary(c => c.Key, c => c.Value, StringComparer.OrdinalIgnoreCase); + return _controllerInfoCache.Value.ToDictionary(c => c.Key, c => c.Value, StringComparer.OrdinalIgnoreCase); } private static IHttpController CreateInstance(HttpControllerContext controllerContext, HttpControllerDescriptor controllerDescriptor) @@ -138,32 +136,39 @@ namespace System.Web.Http.Dispatcher return Error.Format(SRResources.DefaultControllerFactory_ControllerNameAmbiguous_WithRouteTemplate, controllerName, route.RouteTemplate, typeList); } - private void EnsureControllerInfoCacheInitialized() + private ConcurrentDictionary<string, HttpControllerDescriptor> InitializeControllerInfoCache() { - if (!_isControllerInfoCacheInitialized) + var result = new ConcurrentDictionary<string, HttpControllerDescriptor>(); + var duplicateControllers = new HashSet<string>(); + Dictionary<string, ILookup<string, Type>> controllerTypeGroups = _controllerTypeCache.Cache; + + foreach (KeyValuePair<string, ILookup<string, Type>> controllerTypeGroup in controllerTypeGroups) { - lock (_controllerInfoCacheLock) + string controllerName = controllerTypeGroup.Key; + foreach (IGrouping<string, Type> controllerTypesGroupedByNs in controllerTypeGroup.Value) { - if (!_isControllerInfoCacheInitialized) + foreach (Type controllerType in controllerTypesGroupedByNs) { - Dictionary<string, ILookup<string, Type>> controllerTypeGroups = _controllerTypeCache.Cache; - foreach (KeyValuePair<string, ILookup<string, Type>> controllerTypeGroup in controllerTypeGroups) + if (result.Keys.Contains(controllerName)) { - string controllerName = controllerTypeGroup.Key; - foreach (var controllerTypesGroupedByNs in controllerTypeGroup.Value) - { - foreach (var controllerType in controllerTypesGroupedByNs) - { - var controllerDescriptor = new HttpControllerDescriptor(_configuration, controllerName, controllerType); - _controllerInfoCache.TryAdd(controllerName, controllerDescriptor); - } - } + duplicateControllers.Add(controllerName); + break; + } + else + { + result.TryAdd(controllerName, new HttpControllerDescriptor(_configuration, controllerName, controllerType)); } - - _isControllerInfoCacheInitialized = true; } } } + + foreach (string duplicateController in duplicateControllers) + { + HttpControllerDescriptor descriptor; + result.TryRemove(duplicateController, out descriptor); + } + + return result; } } } diff --git a/src/System.Web.Http/HttpServer.cs b/src/System.Web.Http/HttpServer.cs index d614c75b..d7399042 100644 --- a/src/System.Web.Http/HttpServer.cs +++ b/src/System.Web.Http/HttpServer.cs @@ -25,8 +25,9 @@ namespace System.Web.Http private readonly HttpConfiguration _configuration; private readonly HttpMessageHandler _dispatcher; private bool _disposed; - private bool _initialized; + private bool _initialized = false; private object _initializationLock = new object(); + private object _initializationTarget; /// <summary> /// Initializes a new instance of the <see cref="HttpServer"/> class with default configuration and dispatcher. @@ -160,26 +161,20 @@ namespace System.Web.Http private void EnsureInitialized() { - if (!_initialized && !_disposed) + LazyInitializer.EnsureInitialized(ref _initializationTarget, ref _initialized, ref _initializationLock, () => { - lock (_initializationLock) - { - if (!_initialized) - { - Initialize(); + Initialize(); - // Attach tracing before creating pipeline to allow injection of message handlers - ITraceManager traceManager = _configuration.ServiceResolver.GetTraceManager(); - Contract.Assert(traceManager != null); - traceManager.Initialize(_configuration); + // Attach tracing before creating pipeline to allow injection of message handlers + ITraceManager traceManager = _configuration.ServiceResolver.GetTraceManager(); + Contract.Assert(traceManager != null); + traceManager.Initialize(_configuration); - // Create pipeline - InnerHandler = HttpPipelineFactory.Create(_configuration.MessageHandlers, _dispatcher); + // Create pipeline + InnerHandler = HttpPipelineFactory.Create(_configuration.MessageHandlers, _dispatcher); - _initialized = true; - } - } - } + return null; + }); } /// <summary> diff --git a/src/System.Web.Mvc/Async/TaskAsyncActionDescriptor.cs b/src/System.Web.Mvc/Async/TaskAsyncActionDescriptor.cs index 22e8e8b1..8f957861 100644 --- a/src/System.Web.Mvc/Async/TaskAsyncActionDescriptor.cs +++ b/src/System.Web.Mvc/Async/TaskAsyncActionDescriptor.cs @@ -131,8 +131,7 @@ namespace System.Web.Mvc.Async tokenSource.Cancel(); } } - }, - state: null, dueTime: timeout, period: Timeout.Infinite); + }, state: null, dueTime: timeout, period: Timeout.Infinite); } Task taskUser = dispatcher.Execute(controllerContext.Controller, parametersArray) as Task; diff --git a/src/System.Web.Mvc/ControllerTypeCache.cs b/src/System.Web.Mvc/ControllerTypeCache.cs index 7b560f4a..a4a8ca7a 100644 --- a/src/System.Web.Mvc/ControllerTypeCache.cs +++ b/src/System.Web.Mvc/ControllerTypeCache.cs @@ -7,7 +7,7 @@ namespace System.Web.Mvc { private const string TypeCacheName = "MVC-ControllerTypeCache.xml"; - private Dictionary<string, ILookup<string, Type>> _cache; + private volatile Dictionary<string, ILookup<string, Type>> _cache; private object _lockObj = new object(); internal int Count diff --git a/src/System.Web.Mvc/MultiServiceResolver.cs b/src/System.Web.Mvc/MultiServiceResolver.cs index d3506fa2..0eedd385 100644 --- a/src/System.Web.Mvc/MultiServiceResolver.cs +++ b/src/System.Web.Mvc/MultiServiceResolver.cs @@ -6,7 +6,7 @@ namespace System.Web.Mvc internal class MultiServiceResolver<TService> : IResolver<IEnumerable<TService>> where TService : class { - private IEnumerable<TService> _itemsFromService; + private Lazy<IEnumerable<TService>> _itemsFromService; private Func<IEnumerable<TService>> _itemsThunk; private Func<IDependencyResolver> _resolverThunk; @@ -19,6 +19,7 @@ namespace System.Web.Mvc _itemsThunk = itemsThunk; _resolverThunk = () => DependencyResolver.Current; + _itemsFromService = new Lazy<IEnumerable<TService>>(() => _resolverThunk().GetServices<TService>()); } internal MultiServiceResolver(Func<IEnumerable<TService>> itemsThunk, IDependencyResolver resolver) @@ -32,20 +33,7 @@ namespace System.Web.Mvc public IEnumerable<TService> Current { - get - { - if (_itemsFromService == null) - { - lock (_itemsThunk) - { - if (_itemsFromService == null) - { - _itemsFromService = _resolverThunk().GetServices<TService>(); - } - } - } - return _itemsFromService.Concat(_itemsThunk()); - } + get { return _itemsFromService.Value.Concat(_itemsThunk()); } } } } diff --git a/src/System.Web.Mvc/SingleServiceResolver.cs b/src/System.Web.Mvc/SingleServiceResolver.cs index b1f5c344..ae2400c0 100644 --- a/src/System.Web.Mvc/SingleServiceResolver.cs +++ b/src/System.Web.Mvc/SingleServiceResolver.cs @@ -6,7 +6,7 @@ namespace System.Web.Mvc internal class SingleServiceResolver<TService> : IResolver<TService> where TService : class { - private TService _currentValueFromResolver; + private Lazy<TService> _currentValueFromResolver; private Func<TService> _currentValueThunk; private TService _defaultValue; private Func<IDependencyResolver> _resolverThunk; @@ -24,6 +24,7 @@ namespace System.Web.Mvc } _resolverThunk = () => DependencyResolver.Current; + _currentValueFromResolver = new Lazy<TService>(GetValueFromResolver); _currentValueThunk = currentValueThunk; _defaultValue = defaultValue; _callerMethodName = callerMethodName; @@ -40,26 +41,19 @@ namespace System.Web.Mvc public TService Current { - get - { - if (_resolverThunk != null) - { - lock (_currentValueThunk) - { - if (_resolverThunk != null) - { - _currentValueFromResolver = _resolverThunk().GetService<TService>(); - _resolverThunk = null; + get { return _currentValueFromResolver.Value ?? _currentValueThunk() ?? _defaultValue; } + } + + private TService GetValueFromResolver() + { + TService result = _resolverThunk().GetService<TService>(); - if (_currentValueFromResolver != null && _currentValueThunk() != null) - { - throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, MvcResources.SingleServiceResolver_CannotRegisterTwoInstances, typeof(TService).Name.ToString(), _callerMethodName)); - } - } - } - } - return _currentValueFromResolver ?? _currentValueThunk() ?? _defaultValue; + if (result != null && _currentValueThunk() != null) + { + throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, MvcResources.SingleServiceResolver_CannotRegisterTwoInstances, typeof(TService).Name.ToString(), _callerMethodName)); } + + return result; } } } diff --git a/src/System.Web.Mvc/UrlRewriterHelper.cs b/src/System.Web.Mvc/UrlRewriterHelper.cs index 265f7202..5a08542b 100644 --- a/src/System.Web.Mvc/UrlRewriterHelper.cs +++ b/src/System.Web.Mvc/UrlRewriterHelper.cs @@ -9,7 +9,7 @@ namespace System.Web.Mvc private object _lockObject = new object(); private bool _urlRewriterIsTurnedOnValue; - private bool _urlRewriterIsTurnedOnCalculated = false; + private volatile bool _urlRewriterIsTurnedOnCalculated = false; private static bool WasThisRequestRewritten(HttpContextBase httpContext) { |