Streaming large files breaks unexpectidely - c#

In my application, I allow user to download large files approx (100MB's) which gets created dynamically and streams directly to the user without storing on the file system. Although it won't breaks all the time, it happens very rarely and very hard to replicate. In order to test it properly, i created a sandbox environment through following class:
public class MvcController : Controller
{
public ActionResult TestWait(int timeToWait = 6000)
{
return new TestWaitResult(timeToWait);
}
}
public class TestWaitResult : ActionResult
{
int _timeToWait;
object o = new object();
TextWriter output;
int _sleepTimer;
public TestWaitResult(int timeToWait)
{
_timeToWait = timeToWait;
_sleepTimer = 10;
}
public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.Buffer = context.HttpContext.Response.BufferOutput = false;
output = context.HttpContext.Response.Output;
while (true)
{
Log("Executing...");
System.Threading.Thread.Sleep(_sleepTimer * 1000);
if ((_timeToWait = _timeToWait - _sleepTimer) <= 0)
{
Log("Timetowait exceeds");
break;
}
}
}
private void Log(string message)
{
lock (o)
{
output.WriteLine("<br/>{0} : {1}", DateTime.Now.ToString("hh:mm:ss.fff"), message);
output.Flush();
}
}
I tried to create the above code simpler to understand the problem.
I deployed the code on the server
http://msongs-test.apphb.com/mvc/testwait?timeToWait=6000
And, after testing it, i found that the response gets break unexpectedly after 10-15 minutes. And, I don't have any clue where it got breaks. I will be extremely happy to know the root cause of the problem.
PS: Although the code runs fine when i deploy it on my local IIS
Edit1: I checked the logs and found that two consecutive errors have been logged
1. HttpException: Server cannot set status after HTTP headers have been sent.
2. HttpException: The remote host closed the connection. The error code is 0x800704CD.

Related

UWP crash in ntdll.dll

I've got a UWP (C#) app that's running in production on a remote machine (under windows 10) but it periodically crashes.
My client says, somewhat arbitrarily, every 9 hours or so.
I have several .wer files from the previous crashes but did not have a minidump, the paths referenced in the event viewer entry for the crash are blank other than the WER files.
See edits below for how a minidump was obtained and findings.
The exception is an access violation (0xc0000005) at exception offset 0x0004df23 in ntdll.dll
I have the full source for the application and can run it in debug for long periods without the crash.
If I use DLL Export Viewer and load the exact version of ntdll.dll (copied from the remote machine) then I can see that at relative address 0x0004dc60 is EtwNotificationRegister and at 0x0004e260 is LdrGetDllPath.
Does this mean that my crash is occurring within a line of code in EtwNotificationRegister (which in turn is invoked by something within our code; however very difficult to trace without stack/minidump)
I am not sure if the layout of a dll is such that the address I have can be placed like that?
Edit 2 as per #Raymond: No. There are almost certainly other non-exported functions between EtwNotificationRegister and LdrGetDllPath. On build 17763.475, offset 4df23 is RtlpWaitOnCriticalSection, so you are probably using an uninitialized critical section or an already-deleted critical section.
Is there any way I can extract more detail about this crash? I have remote access to the computer running the app but the crash does not appear to be triggered by a particular event (e.g. we can't hit a button and cause the crash)
Using a minidump now
I am running the program in both local debug as well.
I have a remote debugger to the remote process but can't seem to break or inspect threads, not sure why. Just redeployed with symbols and the debugger attaches no problem but it just skips all breakpoints :(
Our own (rather naive) local log file, originally intended for only local debugging is written with a StreamWriter.WriteLine and immediately followed with a StreamWriter.Flush (wrapped in a try catch since that's not thread safe) just ends at a normal event on the remote machine - there is nothing following this normal event.
We catch App_UnhandledException and write to this log so I'd have expected a stack here.
In Unexplained crashes related to ntdll.dll it is suggested that a crash from ntdll.dll is a canary in a coalmine Unexplained crashes related to ntdll.dll
Edit 1: I have configured an auto crash dump as per https://www.meziantou.net/2018/06/04/tip-automatically-create-a-crash-dump-file-on-error so if I can get it to crash again maybe I'll get a dump file next time?
Here is the detail from the WER
Version=1
EventType=MoAppCrash
EventTime=132017523132123596
ReportType=2
Consent=1
UploadTime=132017523137590717
ReportStatus=268435456
ReportIdentifier=8d467f04-4bdd-4f9e-bf26-b42d143ece1a
IntegratorReportIdentifier=b60f9ca0-4126-4262-a886-98d3844892d3
Wow64Host=34404
NsAppName=praid:App
OriginalFilename=XXXXXX.YYYYYY.exe
AppSessionGuid=00001514-0001-0004-9fe2-6df11905d501
TargetAppId=U:XXXXXX.YYYYYY_1.0.201.0_x64__b0abmt6f49vqj!App
TargetAppVer=1.0.201.0_x64_!2018//01//24:08:17:16!1194d!XXXXXX.YYYYYY.exe
BootId=4294967295
TargetAsId=1298
UserImpactVector=271582000
IsFatal=1
EtwNonCollectReason=4
Response.BucketId=2ee79f27e2e81a541d6200d746866340
Response.BucketTable=5
Response.LegacyBucketId=2117255699418735424
Response.type=4
Sig[0].Name=Package Full Name
Sig[0].Value=XXXXXX.YYYYYY_1.0.201.0_x64__b0abmt6f49vqj
Sig[1].Name=Application Name
Sig[1].Value=praid:App
Sig[2].Name=Application Version
Sig[2].Value=1.0.0.0
Sig[3].Name=Application Timestamp
Sig[3].Value=5a68410c
Sig[4].Name=Fault Module Name
Sig[4].Value=ntdll.dll
Sig[5].Name=Fault Module Version
Sig[5].Value=10.0.17763.475
Sig[6].Name=Fault Module Timestamp
Sig[6].Value=3230aa04
Sig[7].Name=Exception Code
Sig[7].Value=c0000005
Sig[8].Name=Exception Offset
Sig[8].Value=000000000004df23
DynamicSig[1].Name=OS Version
DynamicSig[1].Value=10.0.17763.2.0.0.256.48
DynamicSig[2].Name=Locale ID
DynamicSig[2].Value=5129
DynamicSig[22].Name=Additional Information 1
DynamicSig[22].Value=95b1
DynamicSig[23].Name=Additional Information 2
DynamicSig[23].Value=95b15a88b673e33a5f48839974790b1c
DynamicSig[24].Name=Additional Information 3
DynamicSig[24].Value=283d
DynamicSig[25].Name=Additional Information 4
DynamicSig[25].Value=283dea7b6b6112710c1e3f76ed84d993
Edit 3: screenshot of minidump from a crash last night. In the event log, the WER crash looks the same so this appears to be the same issue. I will see if I can load symbols etc.
Edit 4: Attempting to debug managed. Threads view shows a thread as the exception point but no call stack info.
Edit 5: Debugging native from the minidump. Looks like we have a winner.
#Raymond was correct, it was RtlpWaitOnCriticalSection invoked from BluetoothLEAdvertismentWatcher::AdvertismentReceivedCallbackWorker
Native call stack as text:
Not Flagged > 8748 0 Worker Thread Win64
Thread Windows.Devices.Bluetooth.dll!(void)
ntdll.dll!RtlpWaitOnCriticalSection()
ntdll.dll!RtlpEnterCriticalSectionContended()
ntdll.dll!RtlEnterCriticalSection()
Windows.Devices.Bluetooth.dll!(void)()
Windows.Devices.Bluetooth.dll!wil::ResultFromException<(void)
()
Windows.Devices.Bluetooth.dll!Windows::Devices::Bluetooth::Advertisement::BluetoothLEAdvertisementWatcher::AdvertisementReceivedCallbackWorker(void)
Windows.Devices.Bluetooth.dll!Windows::Devices::Bluetooth::Advertisement::BluetoothLEAdvertisementWatcher::AdvertisementReceivedThreadpoolWorkCallbackStatic(struct
_TP_CALLBACK_INSTANCE *,void *,struct _TP_WORK *)
ntdll.dll!TppWorkpExecuteCallback()
ntdll.dll!TppWorkerThread()
kernel32.dll!BaseThreadInitThunk()
ntdll.dll!RtlUserThreadStart()
Edit 6: okay so now, what do I do? How can I resolve this problem? My understanding of the stack is it looks like an exception was thrown inside the callback? Is that correct?
So I could put a managed try/catch in the BLE advertisment callback handler and that should (catch - for further debugging) fix it?
Edit 7: code...
Here is the code we use to instantiate the wrapper and subscribe to events.
The BluetoothLEAdvertisementWatcherWrapper is a delgating class (e.g. it just wraps the underlying BluetoothLEAdvertisementWatcher so it can implement an interface; it simply passes all events through and exposes properties. We do this so that we can have a different version that creates virtual events for testing)
bluetoothAdvertisementWatcher = new BluetoothLEAdvertisementWatcherWrapper();
bluetoothAdvertisementWatcher.SignalStrengthFilter.SamplingInterval = TimeSpan.Zero;
bluetoothAdvertisementWatcher.ScanningMode = BluetoothLEScanningMode.Active;
bluetoothAdvertisementWatcher.Received += Watcher_Received;
bluetoothAdvertisementWatcher.Stopped += Watcher_Stopped;
bluetoothAdvertisementWatcher.Start();
Here is the code for the wrapper; just to show it's not doing anything complex:
public class BluetoothLEAdvertisementWatcherWrapper : IBluetoothAdvertismentWatcher, IDisposable
{
private BluetoothLEAdvertisementWatcher bluetoothWatcher;
public BluetoothLEAdvertisementWatcherWrapper()
{
bluetoothWatcher = new BluetoothLEAdvertisementWatcher();
}
public BluetoothSignalStrengthFilter SignalStrengthFilter => bluetoothWatcher.SignalStrengthFilter;
public BluetoothLEScanningMode ScanningMode
{
get
{
return bluetoothWatcher.ScanningMode;
}
set
{
bluetoothWatcher.ScanningMode = value;
}
}
public event TypedEventHandler<BluetoothLEAdvertisementWatcher, BluetoothLEAdvertisementReceivedEventArgs> Received
{
add
{
bluetoothWatcher.Received += value;
}
remove
{
bluetoothWatcher.Received -= value;
}
}
public event TypedEventHandler<BluetoothLEAdvertisementWatcher, BluetoothLEAdvertisementWatcherStoppedEventArgs> Stopped
{
add
{
bluetoothWatcher.Stopped += value;
}
remove
{
bluetoothWatcher.Stopped -= value;
}
}
public BluetoothLEAdvertisementWatcherStatus Status => bluetoothWatcher.Status;
public Action<IPacketFrame, short> YieldAdvertisingPacket { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public void Start()
{
bluetoothWatcher.Start();
}
public void Stop()
{
bluetoothWatcher.Stop();
}
public void Dispose()
{
if (bluetoothWatcher != null)
{
if (bluetoothWatcher.Status == BluetoothLEAdvertisementWatcherStatus.Started)
{
bluetoothWatcher.Stop();
}
bluetoothWatcher = null;
}
}
}
And here is the code for the Watcher_Received event handler:
private void Watcher_Received(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args)
{
try
{
//we won't queue packets until registered
if (!ApplicationContext.Current.Details.ReceiverId.HasValue)
return;
IPacketFrame frame;
PacketFrameParseResult result = ParseFrame(args, out frame);
if (result == PacketFrameParseResult.Success)
{
ApplicationContext.Current.Details.BluetoothPacketCount++;
}
short rssi = args.RawSignalStrengthInDBm;
string message = FormatPacketForDisplay(args, args.AdvertisementType, rssi, frame, result);
if (BluetoothPacketReceived != null)
{
BluetoothPacketReceived.Invoke(this, new BluetoothPacketReceivedEventArgs(message, result, frame, rssi));
}
}
catch (Exception ex)
{
if (ex.InnerException is Exceptions.PacketFrameParseException && (ex.InnerException as Exceptions.PacketFrameParseException).Result == PacketFrameParseResult.InvalidData)
{
// noop
}
else
{
Logger.Log(LogLevel.Warning, "BLE listener caught bluetooth packet error: {0}", ex);
if (BluetoothPacketError != null)
{
BluetoothPacketError.Invoke(this, new BluetoothPacketErrorEventArgs(ex));
}
}
}
}
You can see here that the entire managed callback is wrapped in a try catch and doesn't rethrow, so I'm not sure if there's anything further I can do to prevent the native exception from bringing the application down.
Current thinking, based on this: RtlpEnterCriticalSectionContended is it a parallel event handler, the native side is raising the handler, and it raises for a new event in the same thread while the previous handler is still executing from a previous event?
Then this is a race condition on the critical section that causes the crash?
Edit 8: To test this theory, I replaced the contents of received with a read + push to a concurrent queue, allowing the managed code to exit the event handler as quickly as possible.
Then added a seperate thread reading from the concurrent queue to perform my application side processing.
Initially, I thought this had resolved the issue as the application actively (listening) ran for approximately 15 hours, however it crashed again this morning with the same symptoms.
Edit 8: Following suggestions in the comments, we tried to ensure that we didn't dispose/GC the watcher after a stop prior to the receive completing.
We did this by using a TaskCompletionSource to function as a promise, subscribing to the Stopped event so we could await on the completion source task which would only have a result set when the Stopped event had fired.
We also used a lock (Monitor.Enter) in both StopAsync and Received to ensure that both could not be running in parallel.
This appeared to reduce the speed at which the system could process events which would make sense if the BLE packets were arriving in parallel.
Updated code as follows:
if ((DateTime.Now - this.LastStartedTimestamp).TotalSeconds > 60)
{
if (this.LastStopReason != BluetoothWatcherStopReason.DeviceCharacteristicWorker)
{
Logger.Log(LogLevel.Debug, "Stopping bluetooth watcher...");
// restart watcher every 10 mins
await this.StopAsync(BluetoothWatcherStopReason.AutomaticRestart);
//start again if automatic restart
Logger.Log(LogLevel.Debug, "Starting bluetooth watcher...");
this.Start(this.testMode);
Logger.Log(LogLevel.Debug, "Started bluetooth watcher");
this.LastStartedTimestamp = DateTime.Now;
}
}
private void Watcher_Stopped(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementWatcherStoppedEventArgs args)
{
string error = args.Error.ToString();
Logger.Log(LogLevel.Warning, string.Format("BLE listening stopped because {0}...", error));
LastError = args.Error;
if (BluetoothWatcherStopped != null)
{
BluetoothWatcherStopped.Invoke(sender, args);
}
}
public class ReceivedBluetoothAdvertismentPacketItem
{
public DateTime Timestamp { get; set; }
public BluetoothLEAdvertisementType Type { get; set; }
public byte[] Buffer { get; set; }
public short Rssi { get; set; }
}
ConcurrentQueue<ReceivedBluetoothAdvertismentPacketItem> BluetoothPacketsReceivedQueue = new ConcurrentQueue<ReceivedBluetoothAdvertismentPacketItem>();
private void Watcher_Received(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args)
{
bool lockWasTaken = false;
try
{
//this prevents stop until we're exiting Received
Monitor.Enter(BluetoothWatcherEventSynchronisation, ref lockWasTaken);
if (!lockWasTaken)
{
return;
}
//we won't queue packets until registered
if (!ApplicationContext.Current.ReceiverDetails.ReceiverId.HasValue)
return;
BluetoothLEAdvertisementType type = args.AdvertisementType;
byte[] buffer = GetManufacturerData(args.Advertisement);
short rssi = args.RawSignalStrengthInDBm;
BluetoothPacketsReceivedQueue.Enqueue(new ReceivedBluetoothAdvertismentPacketItem
{
Timestamp = DateTime.UtcNow,
Type = type,
Rssi = rssi,
Buffer = buffer
});
ApplicationContext.Current.ReceiverDetails.UnprocessedQueueLength = BluetoothPacketsReceivedQueue.Count;
}
catch (Exception ex)
{
Logger.Log(LogLevel.Warning, "BLE listener caught bluetooth packet error: {0}", ex);
if (BluetoothPacketError != null)
{
BluetoothPacketError.Invoke(this, new BluetoothPacketErrorEventArgs(ex));
}
}
finally
{
if (lockWasTaken)
{
Monitor.Exit(BluetoothWatcherEventSynchronisation);
}
}
}
public BluetoothWatcherStopReason LastStopReason { get; private set; } = BluetoothWatcherStopReason.Unknown;
private object BluetoothWatcherEventSynchronisation = new object();
public Task<BluetoothWatcherStopReason> StopAsync(BluetoothWatcherStopReason reason)
{
var promise = new TaskCompletionSource<BluetoothWatcherStopReason>();
if (bluetoothAdvertisementWatcher != null)
{
LastStopReason = reason;
UpdateBluetoothStatusInReceiverModel(BluetoothLEAdvertisementWatcherStatus.Stopped); //actually stopping but we lie
bool lockWasTaken = false;
try
{
Monitor.Enter(BluetoothWatcherEventSynchronisation, ref lockWasTaken);
{
bluetoothAdvertisementWatcher.Received -= Watcher_Received;
bluetoothAdvertisementWatcher.Stopped += (sender, args) =>
{
// clean up
if (bluetoothAdvertisementWatcher != null)
{
bluetoothAdvertisementWatcher.Stopped -= Watcher_Stopped;
bluetoothAdvertisementWatcher = null;
}
//notify continuation
promise.SetResult(reason);
};
bluetoothAdvertisementWatcher.Stop();
}
}
finally
{
if (lockWasTaken)
{
Monitor.Exit(BluetoothWatcherEventSynchronisation);
}
}
}
base.Stop();
return promise.Task;
}
Following these changes, the same crash is still occuring in the Windows.Devices.Bluetooth native assembly (as per above)
Edit 9: I've removed the automatic periodic start/stop and now the app has been stable for > 36 hours without a crash. So something inside this flow is causing the crashes. We originally added that to work around an issue with the advertisment watcher just stopping after a while, so we'd like to keep it if we can fix it.
The if statement if ((DateTime.Now - this.LastStartedTimestamp).TotalSeconds > 60) (and block) is currently commented.
I have opened a bug for windows universal here: https://wpdev.uservoice.com/forums/110705-universal-windows-platform/suggestions/37623343-bluetoothleadvertismentwatcher-advertismentreceiv

How to properly recycle my TCP port using HttpListener c#

Per the comments, and my own testing, I am certain that my initial TCP port is not clearing out, and I don't really know how to remedy it.
I tried to run through the prefixes like so within my refresh method:
foreach (string item in myWebListener.Prefixes) {
if (item.Contains("http://127.0.0.1:5000")) {
myWebListener.Prefixes.Remove(item);
}
}
This did not make any noticeable change in the behavior.
The only other thing that was popping up in the web logs was something about the favicon.ico file, and just maybe another browser thread is trapping my connection trying to retrieve this and was going to provide it as an async callback to finish things out, though I didn't provide it so this line of thinking makes sense to me (How to provide a favicon.ico file in my byte stream as an embedded resource is My next research project).
If this shows no changes, my next research project is trying to leverage TCPListener:
I ran across this Q/A, but it really didn't help me as I am not familiar with the TCP way of doing what I have done here, and the work-around looks similar to what I was already attempting in my original code.
There was a mention of a using statement in the linked Q/A, and my guess is that it would look something like:
using(TCPListener myTCPListener = new TCPListener()) {
//Set the necessary TCP statements here.
//Do what I already did with my existing code here.
}
That's as far as my brain can take me so far, does this sound far off based on the factors at play here?
BELOW WAS THE ORIGINAL QUESTION CONTENT:
I am successfully reaching my running C# HttpListener localhost server, and successfully sending information back out to the local requesting browser page.
But after the first time, I completely lock up the browser web page waiting for a new response.
I debug with no errors on VS, so I don't even know where to look next; Hence that's why I'm here.
My hope is, is that someone can shed some light on how to refresh my server internally, so when the new query is needed, I can respond to it just like it was the first time.
I'm using an http get method to call this, so the call would look similar to the following:
"http://127.0.0.1:5000/?param1=searchForThings&param2=itemToSearchFor";
I have to rip out a lot of proprietary code, but here is what is happening:
public class myWebServer {
public myWebServer() {
refresh();
//The Global is being set When the API
// Loads & is referenced here
ev1 = _MyGlobalEvents.ev1
}
public static HttpListener myWebListener { get; set; }
public static HttpListenerContext context { get; set; }
public static AsyncCallback myGetCallback { get; set; }
public static HttpResponseMessage myResp { get; set; }
public static Stream output { get; set; }
public static MyAPIDefinedEventType ev1 { get; set; }
public static void refresh() {
if (myWebListener != null) {
myWebListener.Stop();
myWebListener.Close();
}
if (output != null) { output.Dispose(); }
if (myGetCallback != null) { myGetCallback = null; }
myWebListener = new HttpListener();
myGetCallback = new AsyncCallback(processRequest);
myWebListener.Prefixes.Add("http://127.0.0.1:5000/");
myWebListener.Start();
myResp = new HttpResponseMessage(HttpStatusCode.Created);
myWebListener.BeginGetContext(myGetCallback, myResp);
}
public static void processRequest(IAsyncResult result) {
context = myWebListener.EndGetContext(result);
context.Response.KeepAlive = true;
myResp = new HttpResponseMessage(HttpStatusCode.Accepted);
context.Response.StatusCode = (int)HttpStatusCode.Accepted;
output = context.Response.OutputStream;
string label = context.Request.QueryString["param1"];
string fiStr = context.Request.QueryString["param2"];
if (label != null || label != "" || label.Contains("\n") == false) {
int pass1 = 0;
int pass2 = 0;
try {
int myInt = label.ToCharArray().Length;
pass1 = 1;
} catch { }
if (pass1 > 0) {
try {
int myInt2 = fiStr.ToCharArray().Length;
pass2 = 1;
} catch { }
}
if ((pass1 == 1 && pass2 == 0) || (pass1 == 1 && pass2 == 1)) {
pass1 = 0;
pass2 = 0;
if (label == "searchForThings" && (fiStr != null && fiStr != "")) {
string respStr = "<html><body><div id=\"status_msg\" style=\"display:inline;\">" + fiStr + "</div></body></html>";
byte[] respBuffer = Encoding.ASCII.GetBytes(respStr);
context.Response.StatusCode = (int)HttpStatusCode.Accepted;
output.Write(respBuffer, 0, respBuffer.Length);
_MyGlobalEvents.searchStr = fiStr;
ev1.Raise();
}
}
}
}
//When the Custom Event is done processing it runs something like the
//following to clean up and finalize
public void _processSearch(string resultStr) { processSearch(resultStr); }
public static void processSearch(string resultStr) {
string respStr = "<html><head>" +
"<style type=\"text/css\">" +
"tr.dispRow{ display:table-row; }\n" +
"tr.noRow{ display:none; }" +
"</style>" +
"</head>" +
"<body>" +
resultStr +
"</body></html>";
byte[] respBuffer = Encoding.ASCII.GetBytes(respStr);
output.Flush();
context.Response.StatusCode = (int)HttpStatusCode.OK;
output.Write(respBuffer, 0, respBuffer.Length);
output.Close();
context.Response.KeepAlive = false;
context.Response.Close();
refresh();
}
}
public static class _MyGlobalEvents {
public static string searchStr { get; set; }
public static MyAPIDefinedEventType ev1 { get; set; }
}
"But after the first time, I completely lock up the browser web page waiting for a new response" - are you saying the error is seen on the browser sending an Http request but getting no response ?
I'd advise downloading and using Telerik Fiddler (free) to see whats actually being sent and received between the client and server.
As other respondees have said - this is not a normal pattern for HttpListener and I'd guess you are having problems with TCP port caching on the server, and your .Stop() and .Close() methods are not freeing the TCP port (Windows caches it)
My particular problem was actually the favicon.ico file request from the browser locking up the connection.
Early on I was noticing that my server was being hit twice, and that is why I had my flags in place to make certain that I was only operating on what was the actual Get request based on the URL I was expecting...
Otherwise the URL was erroring as a blank string (turns out this was the favicon.ico request being made - irritating, because when the string is completely empty you have no idea how to troubleshoot it). So I was ignoring it and not touching what I didn't understand.
Well it turns I could NOT ignore this request, because the browser is expecting a response for it. All I had to do was put an else section in my code that executed if my flag options weren't met, and close, dispose and refresh the connection when this happened.
This Q/A really described the solidified solution to the problem fairly well, but my Exception.Message was blank which didn't really get addressed there either, so I'm glad I get to document it here in the hopes that anyone else who may ever run into the same problem finds the answer a little more strait forward.
As for the answer that PhillipH provided, this proved to be the most helpful to me, as it revealed to me that I needed to be checking what was happening from the network traffic eventing.
However I could not really use the tools that this user suggested, but found an alternative with Google Chrome's built in chrome://net-internals/. Just type that into your address bar in chrome and execute it.
Anyway way this was the total answer for me, but PhillipH you deserve it for moving me in the right direction.

WCF+ duplex + Windows Form Application (ButtonClick) Trouble

I have WCF classes and now project to do. My teacher gave as to write app with streaming and duplex (I know that is impossible, but I found backdoor from this situation - I'm sending pics under 60KB).
My code worked well so far as I start wrote my GUI in Windows Form Application.
When I'm testing it via console - everything work well. But, when I want to use buttons in my GUI i have this exception:
An unhandled exception of type 'System.TimeoutException' occurred in
mscorlib.dll Additional information: This request operation sent to
net.tcp://localhost:7756/Przesylanie did not receive a reply within
the configured timeout (00:00:59.9740007). The time allotted to this
operation may have been a portion of a longer timeout. This may be
because the service is still processing the operation or because the
service was unable to send a reply message. Please consider
increasing the operation timeout (by casting the channel/proxy to
IContextChannel and setting the OperationTimeout property) and ensure
that the service is able to connect to the client.
Here bunch of code:
Service + IService (due to limitation of Stack I put it to one file):
public void WyslijstrumienNaSerwer()
{
IPrzesylanieCallback callback = OperationContext.Current.GetCallbackChannel<IPrzesylanieCallback>();
string sciezka = #"E:\5 semestr\Fras\Se płotek\Lab6\WcfServiceContractZadanie2\Host\bin\Pliki\" + "plik_odebrany.jpg";
string filePath = Path.Combine(System.Environment.SystemDirectory, sciezka);
Console.WriteLine("start");
callback.WyslijStrumien(filePath);
Console.WriteLine(filePath);
Console.WriteLine("meta");
}
namespace WcfServiceContractZadanie2
{
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IPrzesylanieCallback))]
public interface IPrzesylanie
{
[OperationContract]
void WyslijstrumienNaSerwer();
}
[ServiceContract]
public interface IPrzesylanieCallback
{
[OperationContract(IsOneWay = true)]
void WyslijStrumien(string filePath);
}
}
Client + callback + form + References.cs:
namespace Aplikacja
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
PrzesylanieClient klient = new PrzesylanieClient(new InstanceContext(new PrzesylanieCallback()), "NetTcpBinding_IPrzesylanie");
klient.WyslijstrumienNaSerwer();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
public class PrzesylanieCallback : Referencja1.IPrzesylanieCallback
{
public void WyslijStrumien(string filePath)
{
Form1 o1 = new Form1();
// Pobieranie obrazka od klienta
string sciezka = #"E:\5 semestr\Fras\Se płotek\Lab6\WcfServiceContractZadanie2\Aplikacja\bin\Pliki\" + o1.wybrany();
string filePathZrodla = Path.Combine(System.Environment.SystemDirectory, sciezka);
//Otwieranie obrazka
Stream strumien = null;
try
{
FileStream imageFile = File.OpenRead(filePathZrodla);
strumien = (Stream)imageFile;
}
catch(IOException ioe)
{
Console.WriteLine("Wyjatek otwierania pliku: {0}", ioe.ToString());
Console.ReadKey();
throw ioe;
}
// Zapisywanie obrazka
o1.SaveFile(strumien, filePath);
}
}
}
private void btnPrzeslijPlikNaSerwer_Click(object sender, EventArgs e)
{
PrzesylanieClient klient = new PrzesylanieClient(new InstanceContext(new PrzesylanieCallback()), "NetTcpBinding_IPrzesylanie");
klient.WyslijstrumienNaSerwer();
}
public void WyslijstrumienNaSerwer() {
base.Channel.WyslijstrumienNaSerwer();
}
I wrote methods SaveFile which works correctly.
As you see, I'm testing my code in the begining of Main function in Client and that works well.
But when I'm using the same code in Forms it does not work. Compiler is returnig me to References.cs and gives me exception I mentioned earlier.
Waiting for any respone!
My answer does not solve your exception issue; however, it might prevent you from getting a very bad grade. Streaming AND duplex are both supported in WCF.
Streaming:
https://msdn.microsoft.com/en-us/library/ms733742(v=vs.110).aspx
Duplex:
https://msdn.microsoft.com/en-us/library/ms731064(v=vs.110).aspx

SignalR OnDisconnected - a reliable way to handle "User is Online" for chatroom?

I'm implementing a chat room. So far, so good - users can send messages from their browsers via a JS client, and I can use a C# client to do the same thing - these messages get broadcast to other users. Now, I'm trying to implement "online users".
My approach is the following:
OnConnected - update the User in the db to be IsOnline = true
OnDisconnected - if the User doesn't have any other connections, update the user in the db to be IsOnline = false
I'm storing state in the DB because I have to query the db for user thumbnails anyways - this seemed like a simple alternative to working with the Dictionaries in the hub.
The problem I'm encountering is that OnDisconnected doesn't always get called for every client Id - the stale connections are preventing the "if the user doesn't have any other connections" bit from resolving to true, so the user is always "online".
One hacky solution I can think of is to always set the user to offline in the db upon OnDisconnect - but this means that if the user opens two tabs and closes one, they will be "offline". I could then re-set the user to online for every message that gets sent, but this seems like a total waste of processing cycles and still leaves a chunk of time where the user is seen as offline, when they are really online.
I believe that if there was a way to guarantee that OnDisconnected gets called for every client, this problem would go away. It seems like if I leave clients open for a long time (> 10 minutes) and then disconnect, OnDisconnected never gets called. I'll try my best to pinpoint the repro steps and keep this updated.
So - Is this a valid approach to handling online status? If so, what else can be done to ensure that OnDisconnected is firing for every connection, eventually?
This problem worries me because existing Connections will just continue to grow over time, if I'm not mistaken, eventually overflowing due to unhandled state connections.
Code:
I'm using the In-memory approach to groupings.
Sending Messages (C#):
private readonly static ConnectionMapping<string> _chatConnections =
new ConnectionMapping<string>();
public void SendChatMessage(string key, ChatMessageViewModel message) {
message.HtmlContent = _compiler.Transform(message.HtmlContent);
foreach (var connectionId in _chatConnections.GetConnections(key)) {
Clients.Client(connectionId).addChatMessage(JsonConvert.SerializeObject(message).SanitizeData());
}
}
State management:
public override Task OnConnected() {
HandleConnection();
return base.OnConnected();
}
public override Task OnDisconnected() {
HandleConnection(true);
return base.OnDisconnected();
}
public override Task OnReconnected() {
HandleConnection();
return base.OnReconnected();
}
private void HandleConnection(bool shouldDisconnect = false) {
if (Context.User == null) return;
var username = Context.User.Identity.Name;
var _userService = new UserService();
var key = username;
if (shouldDisconnect) {
_chatConnections.Remove(key, Context.ConnectionId);
var existingConnections = _chatConnections.GetConnections(key);
// this is the problem - existingConnections occasionally gets to a point where there's always a connection - as if the OnDisconnected() never got called for that client
if (!existingConnections.Any()) { // THIS is the issue - existingConnections sometimes contains connections despite there being no open tabs/clients
// save status serverside
var onlineUserDto = _userService.SetChatStatus(username, false);
SendOnlineUserUpdate(_baseUrl, onlineUserDto, false);
}
} else {
if (!_chatConnections.GetConnections(key).Contains(Context.ConnectionId)) {
_chatConnections.Add(key, Context.ConnectionId);
}
var onlineUserDto = _userService.SetChatStatus(Context.User.Identity.Name, true);
SendOnlineUserUpdate(_baseUrl, onlineUserDto, true);
// broadcast to clients
}
}
ConnectionMapping:
public class ConnectionMapping<T> {
private readonly Dictionary<T, HashSet<string>> _connections =
new Dictionary<T, HashSet<string>>();
public int Count {
get {
return _connections.Count;
}
}
public void Add(T key, string connectionId) {
lock (_connections) {
HashSet<string> connections;
if (!_connections.TryGetValue(key, out connections)) {
connections = new HashSet<string>();
_connections.Add(key, connections);
}
lock (connections) {
connections.Add(connectionId);
}
}
}
public IEnumerable<string> GetConnections(T key) {
HashSet<string> connections;
if (_connections.TryGetValue(key, out connections)) {
return connections.ToList();
}
return Enumerable.Empty<string>();
}
public void Remove(T key, string connectionId) {
lock (_connections) {
HashSet<string> connections;
if (!_connections.TryGetValue(key, out connections)) {
return;
}
lock (connections) {
connections.Remove(connectionId);
if (connections.Count == 0) {
_connections.Remove(key);
}
}
}
}
}
Update
Per dfowler's suggestion, an alternative approach would be to implement in-db mapping instead of in-memory, this way more metadata can be used to identify zombified connections. I'm hoping for a solution to the in-memory problem though, instead of re-architect away from a recommended approach that's already implemented.
Try following this sample here:
https://github.com/DamianEdwards/NDCLondon2013/tree/master/UserPresence

What is causing this DatabaseFileLockedException when trying to open a db4o database in an ASP.NET MVC app?

I'm building a small web application with ASP.NET MVC 2, using db4o as a datastore.
I have added an HttpModule—as per the example here—to give the application access to the db4o database, and everything is working perfectly on my development machine under the VS2008 ASP.NET Development Server.
However, when I deploy the app to my web host and try to access it, I get a DatabaseFileLockedException at the line where the HttpModule tries to open the database file. But there should be nothing else accessing the file; indeed on first run of the app it will only just have been created when this exception gets thrown.
The web host's servers are running IIS 7 on Windows Server 2008, and the application is running under Full Trust. It is a sub-application, in case that makes any difference.
I can't work out why this error is occurring on the live server, but not locally on my development server. Can anyone help me out or suggest what I should do next?
That's a mistake in the example-code. It assumes that the HttpModule.Init is only called once, which isn't necessarily true. Depending how your application is configured, it can be called multiple times. To fix this, check in the HttpModule-Handler if the instance is already there:
using System;
using System.Configuration;
using System.Web;
using Db4objects.Db4o;
namespace Db4oDoc.WebApp.Infrastructure
{
public class Db4oProvider : IHttpModule
{
private const string DataBaseInstance = "db4o-database-instance";
private const string SessionKey = "db4o-session";
// #example: open database when the application starts
public void Init(HttpApplication context)
{
if (null==HttpContext.Current.Application[DataBaseInstance])
{
HttpContext.Current.Application[DataBaseInstance] = OpenDatabase();
}
RegisterSessionCreation(context);
}
private IEmbeddedObjectContainer OpenDatabase()
{
string relativePath = ConfigurationSettings.AppSettings["DatabaseFileName"];
string filePath = HttpContext.Current.Server.MapPath(relativePath);
return Db4oEmbedded.OpenFile(filePath);
}
// #end example
// #example: close the database when the application shuts down
public void Dispose()
{
IDisposable toDispose = HttpContext.Current.Application[DataBaseInstance] as IDisposable;
if (null != toDispose)
{
toDispose.Dispose();
}
}
// #end example
// #example: provide access to the database
public static IObjectContainer Database
{
get { return (IObjectContainer)HttpContext.Current.Items[SessionKey]; }
}
// #end example
// #example: A object container per request
private void RegisterSessionCreation(HttpApplication httpApplication)
{
httpApplication.BeginRequest += OpenSession;
httpApplication.EndRequest += CloseSession;
}
private void OpenSession(object sender, EventArgs e)
{
IEmbeddedObjectContainer container =
(IEmbeddedObjectContainer)HttpContext.Current.Application[DataBaseInstance];
IObjectContainer session = container.OpenSession();
HttpContext.Current.Items[SessionKey] = session;
}
private void CloseSession(object sender, EventArgs e)
{
if (HttpContext.Current.Items[SessionKey] != null)
{
IObjectContainer session = (IObjectContainer)HttpContext.Current.Items[SessionKey];
session.Dispose();
}
}
// #end example
}
}
As alternative you could use the Application_Start from the Global.apsx, which is called only once for sure.
You have another problem here.
When AppPools restart there can be an overlap when the old AppPool is finishing request and the new AppPool is servicing new requests.
During this time you will have two processes trying to access the same db4o file
To get around this you can use something like the hack below.
Note the use of Db4oFactory.OpenServer instead of Db4oEmbedded.OpenFile. This allows the use of transactions on a more fine grained basis.
public IObjectServer OpenServer()
{
Logger.Debug("Waiting to open db4o server.");
var attempts = 0;
do
{
try
{
return Db4oFactory.OpenServer(fileName, 0);
}
catch (DatabaseFileLockedException ex)
{
attempts++;
if (attempts > 10)
{
throw new Exception("Couldn't open db4o server. Giving up!", ex);
}
Logger.Warn("Couldn't open db4o server. Trying again in 5sec.");
Thread.Sleep(5.Seconds());
}
} while (true);
}
Hope this helps
Sounds like permission issues if it works on dev. Stick a notepad file in the same directory and try to open that with some bare bones file code. I bet you'll have the same issue.

Categories