process exit event and thread synchronization

  • Thread starter Thread starter Guest
  • Start date Start date
G

Guest

I hope someone could help me with a hint, maybe I am having a problem to
conceptually understand whats going on:

I am starting a new process (.NET20, c#, command line app) which launches an
external cmd shell and some external code (.NET remoted into from my manager
code).

I am subscribing to the exit event of the process to maintain a list of
shells I have running. I also have code which can cause the remote code
running in the shell to exit (which then also fires the exit event).

I tried to use a mutex and a sempahore (same results) to prevent the event
and my manager code run concurrently - and run into a deadlock.

Analyzing this it seems the deadlock is caused because both the event and my
manager code are actually running under the same thread - which seems a
surprise to me as I was expecting the event from a different either process
or at least thread. Apparently the event is more like an interrupt and not a
separate thread or even process???

Guess I'm confused as to what's going on here - maybe someone can drop a
line of help?

Thanx ahead!

Theo
 
[...]
Analyzing this it seems the deadlock is caused because both the event and my
manager code are actually running under the same thread - which seems a
surprise to me as I was expecting the event from a different either process
or at least thread. Apparently the event is more like an interrupt and not a
separate thread or even process???

There is no particular rule about where an event must execute. It will
be executed in whatever thread raised the event, but different classes
do different things to control which thread that is.

For sure, an event will never be raised in a different process. Your
code will always execute in the process in which it exists, for a
variety of reasons. But your code's process can have a variety of
threads.

As far as the specific thread here goes, it appears that the event to
which you've subscribed has some specific logic in it to raise the
event on the same thread used to create the managed object. This is
not unheard of; for example, the BackgroundWorker class also does this
with some of its events, for the convenience of the creating thread.

Now, is this really the issue you're running into? While I'm not that
familiar with the events on the Process class, it seems unlikely. In
particular, a given thread cannot deadlock itself. So if you're really
running into deadlock, the event is definitely _not_ being raised on
the same thread where you have other synchronized code (i.e. your
manager code).

How best to deal with the deadlock is hard to say, since you haven't
provided any specific information about your implementation. I will
point out that unless you have _two_ mutually dependent synchronized
objects, you can't have deadlock. Sometimes that means that the
solution involves getting rid of one of the synchronized objects.
Alternatively, since your post only describes a single instance of
synchronization, it seems possible that you're incorrect about the
deadlock and that something else is going on.

The best thing would be for you to post a concise-but-complete sample
of code that reliably demonstrates the problem. Then we can actually
look at the synchronization you're doing and understand better what
might be wrong about it.

Pete
 
Pete, thanks a lot for your feedback.

Following the code snippets. This code will not cause a lock as it now uses
a non blocking lock that is returning true for ownership success. However
using a blocking mutex caused a deadlock, apparently because the event
appeared under the same thread. So in someplace my shutdown code must have
been interrupted to fire (or better propagate the OS) process exit event and
in turn the same thread was apparently halted when the event entered the
handling routine and tried to wait on the semaphore - which was already owned
by the shutdown code section.

Thanks again for lighting the darkness ;-)

br

Theo

private void ShellStart()
{
string arg = HostName + @" c:/mydir/mycode.exe -h";
ProcessStartInfo psi = new ProcessStartInfo("ssh", arg);
psi.WindowStyle = ProcessWindowStyle.Hidden;
psi.CreateNoWindow = false;
Process p = new Process();
_session.Shell = p;
p.StartInfo = psi;
_session.Shell.Start();
}

I tried to run ShellStart() both directly or under a new worker Thread like
this
.....
st = new Thread(new ThreadStart(this.ShellStart));
st.Start();
....

The result is the same blocking.

Here's the event callback - Lock for now is a 'homebuilt' mutex, it returns
true if the caller became owner. I had the mutex at the exact same place
before but it would not return as I wrote due to deadlock.

void SessionExited(object sender, EventArgs e)
{
Manager.con.WriteLine("E Event SessionExited ENTRY " + HostName);
Manager.con.WriteLine("E Event thread " +
Thread.CurrentThread.ManagedThreadId);

if (Lock(true))
{
try
{
Manager.con.WriteLine(
String.Format("E Session Exit on {0} {1}", HostName,
(_session.Shell != null ?
("exit code " + _session.Shell.ExitCode.ToString()) :
"")));
}
catch { }
try
{
if (_session.RemoteManager.IsAlive())
_session.RemoteManager.ShutdownServer();
}
catch { }
finally
{
_session.RemoteManager = null;
_session.Shell = null;
Manager.con.WriteLine("E Event SessionExited DONE " +
HostName);
Lock(false);
}
}
else
{
Manager.con.WriteLine("E Event Exit blocked " + HostName);
}
}

And here's the manual Endsession() routine:

public void EndSession()
{
Manager.con.WriteLine("S EndSession ENTRY " + HostName);
Manager.con.WriteLine("S EndSession thread " +
Thread.CurrentThread.ManagedThreadId);

while (!Lock(true))
{
Thread.Sleep(100);
}
if (this.Alive && this.Active && InShellSession &&
InRemoteSession)
{
Console.WriteLine("S Ending Remote Session on host " +
HostName);

//this btw. seems to have no effect - I still keep getting exit events
despite having Lock on
_session.Shell.EnableRaisingEvents = false;
try
{
SetChannel();
if (InRemoteSession)
_session.RemoteManager.ShutdownServer();
}
//will always be thrown as the connection dies from calling rm.ShutdownServe
catch (WebException) { }
catch (Exception e)
{
Console.WriteLine("Shutdown Exception for " + HostName);
Console.WriteLine(e.Message);
}
finally
{
_session.RemoteManager = null;
}

try
{
if (_session._isMyShell)
{
int retries = 100; //10s
while (!_session.Shell.HasExited && --retries > 0) {
Thread.Sleep(100); }
if (!_session.Shell.HasExited)
{
_session.Shell.Kill();
_session.Shell = null;
}
}
}
catch
{
Console.WriteLine("Unable to kill shell for " + HostName);
}
finally
{
_session.Shell = null;
Manager.con.WriteLine("S EndSession EXIT " + HostName);
Lock(false);
}
}
}

This is "my" non blocking access synchronizing mutex

private bool _locked = false;
private bool Lock(bool obtain)
{
lock (this)
{
if (!obtain)
return _locked = false;

if (_locked)
return false;
else
return _locked = true;
}
}
 
Pete, thanks a lot for your feedback.

Following the code snippets. This code will not cause a lock as it now uses
a non blocking lock that is returning true for ownership success. However
using a blocking mutex caused a deadlock, apparently because the event
appeared under the same thread.

As I wrote, a single thread cannot deadlock itself.

As I also wrote, you cannot get the best advice until you post a
concise-but-complete sample of code that reliably reproduces the
problem. Until you do that, one can only guess.

Now, that said, a couple of points:

1) You should not write your own "homebuilt mutex". Use the
built-in synchronization objects, and use them correctly.

2) It's hard to review the code you posted, because of all the
extraneous stuff in it. However, one thing I notice is that in your
EndSession() method, you only release the lock if you enter the true
clause of the if() statement following the acquisition of the lock. If
the expression in the if() statement evaluates to false, you don't
release the lock. That could certainly cause a problem if you expect
to be able to obtain the lock later in a different thread.

Pete
 
Pete,
thanks again - point taken; here's the minimum code needed to simulate the
deadlock. All of this is in a single module host.cs which basically is a
wrapper around a cmd or ssh shell.

Semaphore s = new Semaphore(1, 1);

public void EndSession()
{
Manager.con.WriteLine("S EndSession ENTRY " + HostName);
Manager.con.WriteLine("S EndSession thread " +
Thread.CurrentThread.ManagedThreadId);
s.WaitOne();
Manager.con.WriteLine("S EndSession entered");
//now some more shutdown code

//this line is what I could trace it to - apparently the Sleep allows the
event to come in:

Thread.Sleep(100);

s.Release();
}
 
Pete,
thanks again - let me be more precise:

Here's the minimum code to cause the deadlock - all in a single module, a
wrapper around a cmd or ssh process. The remote process is terminated by a
..net remoting call, it exits and accordingly the local shell exits in
sequence. All that works fine.

//blocks when reaching 0 - so we have one attempt 'for free'
Semaphore s = new Semaphore(1, 1);

//Event handler
void SessionExited(object sender, EventArgs e)
{
Manager.con.WriteLine("E claim s " + HostName);
Manager.con.WriteLine("E Event thread " +
Thread.CurrentThread.ManagedThreadId);
s.WaitOne();
Manager.con.WriteLine("E Event SessionExited ENTRY " + HostName);
//some more shutdown code
Manager.con.WriteLine("E Event Exit done - release lock");
s.Release();
}

//Shutdown method

public void EndSession()
{
Manager.con.WriteLine("S EndSession ENTRY " + HostName);
Manager.con.WriteLine("S EndSession thread " +
Thread.CurrentThread.ManagedThreadId);
s.WaitOne();
Manager.con.WriteLine("S EndSession entered");

//Some more shutdown code - including a moment of task switch:
Thread.Sleep(100);
Manager.con.WriteLine("S Shutdown Exit done - release lock");
s.Release();
}


This is the output - I numbered the lines: the event fires under (4) and the
main thread (ManagedThreadID 10) is not returning; this happens as I wrote
above when I have a Thread.Sleep task switch. According to the
ManagedThreadId this is the same thread - which seems to make sense to
explain the final lockup (the thread is correctly halted because the sem is
occupied). But then according to your statement it should not lock - so it
must be a differnet thread.

1 S EndSession ENTRY z1
2 S EndSession thread 10
3 S EndSession entered
4 E claim s z1
5 E Event thread 10

Anyways, hopefully this is a more precise description so that the issue can
be explained?

Best regards
Theo
 
pls disregard the 6:20AM message - I hit some key which in turn fired the
posting too early. @!"§$%&/()=?@
sorry
tb
 
[...]
Anyways, hopefully this is a more precise description so that the issue can
be explained?

Well, it wasn't quite a "concise-but-complete" sample of code (it was
incomplete). However, it was enough to at least indicate one potential
problem: I had missed that you were using a semaphore in one of your
tests (you did mention that in your first post, but your later post
only discussed a mutex).

You wrote that the same thing happens when using a mutex, however the
two are not the same. A semaphore is the exception to the general rule
that a thread can't block itself, because the semaphore's behavior is
specifically to restrict acquisition to a particular number of times.

However, if you change the code you posted to use a mutex instead,
there should be no such problem. A single thread can acquire and
release a mutex arbitrarily many times (for all practical purposes
anyway).

So if the problem still happens when you use a mutex, then there's
something else going on. And if that's the case, then the code you
posted isn't going to be sufficient for solving the problem.

Pete
 
Pete,

thanks again, I had hoped my code snip was sufficient - if you could let me
know what concisely ;-) you're looking for I'll be glad to post it.

The semaphore is doing exactly what I expected it to do: it prevents entry
into the code while I was hoping I could release it when done in my main
thread. Purpose was to synchronize two distinctly separate code blocks (so
that only one can be in execution at the same time). I don't really care if I
am in the same thread context or in different threads. Actually I was
expecting to be in different ones - because it looks as if I am locking my
own thread with the mutex and the CLR is not getting me back to where I gave
up control.

The Managed ThreadId actually indicates that the event is fired under the ID
of the main thread I was running -> under debugger that is until the moment
when I put my main thread to sleep.

I guess the issue is in the Process event itself or the thread scheduler -
it just should not fire under the ID of the user thread. To me this looks
like a CLR issue.
Or is it legal for any same thread to be in two code places almost at the
same time?

Anyways, guess we've run out of options and I have to use my 'homebuilt'
mutex. Maybe one could write the event handler such that it propagates onward
by firing its own thread, but that's probably a bit of overkill then too. (I
tried btw. to create the Process that fires the events under a worker thread
as well - no change in deadlock.)

I can retry the same using a mutex tomorrow, I tried that before as I said
with the same resulting deadlock.

Maybe someone can pick this up from here and see what's going on inside?

br
Theo
 
Pete,

thanks again, I had hoped my code snip was sufficient - if you could let me
know what concisely ;-) you're looking for I'll be glad to post it.

"Concise" means that you post the bare minimum of code required to
demonstrate the problem. "Complete" means that you post _everything_
that is required to demonstrate the problem. Someone should be able to
compile the code and run it, without any additions or changes, and
there should be nothing in the code that distracts or otherwise makes
it difficult to understand what's going on.
The semaphore is doing exactly what I expected it to do: it prevents entry
into the code while I was hoping I could release it when done in my main
thread.

Well, for sure a semaphore won't work if both sections of code are run
in the same thread.
Purpose was to synchronize two distinctly separate code blocks (so
that only one can be in execution at the same time). I don't really care if I
am in the same thread context or in different threads.

A thread can only execute one statement of code at a time. So if both
sections of code are running in the same thread, they are automatically
synchronized. You can be assured that if one somehow allows another to
execute, you aren't going to get back to the first section until the
second is done. If the second section waits on something that will
only be done by the first section, then yes...your code has essentially
deadlocked itself. It's not easy to do that with a single thread, but
a semaphore will.

Code executing in different threads can deadlock for real, but only if
they are waiting on each other. Deadlocking code is a sign of bad
design. The fix is to correct the design.
Actually I was
expecting to be in different ones - because it looks as if I am locking my
own thread with the mutex and the CLR is not getting me back to where I gave
up control.

You haven't posted enough code for anyone to comment on what's actually
happening. Suffice to say, the code you posted won't deadlock using a
mutex assuming it's all run in the same thread.
The Managed ThreadId actually indicates that the event is fired under the ID
of the main thread I was running -> under debugger that is until the moment
when I put my main thread to sleep.

I guess the issue is in the Process event itself or the thread scheduler -
it just should not fire under the ID of the user thread. To me this looks
like a CLR issue.

IMHO, it looks to me like a design problem in your own code. I'm not
really sure how the Process event deals with raising events, but I
suspect that what's going on is that the process event is raised once
the main thread enters what is called "an alertable wait state", in
which the OS can essentially run a callback within the same thread.
Calling Thread.Sleep() can do that. This is why the event is raised in
the same thread.

This is not "a CLR issue" so much as it is a symptom of a badly
designed piece of code. At a minimum, the attempt to synchronize is
wrong, and it may be that there is simply a better way to accomplish
whatever it is you are trying to accomplish.

Without a complete code sample, it's hard to see what is really trying
to be done here though. Also, you should be more clear about why it is
you feel these sections of code need to be synchronized. What are they
doing that conflict with each other?
Or is it legal for any same thread to be in two code places almost at the
same time?

It is not only illegal, it's impossible. Execution in a given thread
can take place only in a single spot in the code at any given time.
Anyways, guess we've run out of options and I have to use my 'homebuilt'
mutex.

I don't see how your "homebuilt mutex" changes any of the blocking
issues, and I definitely don't think it's the right solution in any
case.
Maybe one could write the event handler such that it propagates onward
by firing its own thread, but that's probably a bit of overkill then too. (I
tried btw. to create the Process that fires the events under a worker thread
as well - no change in deadlock.)

I can retry the same using a mutex tomorrow, I tried that before as I said
with the same resulting deadlock.

Maybe someone can pick this up from here and see what's going on inside?

Maybe. But only if you post a concise-but-complete example of code
that reliably demonstrates the problem.

Pete
 
Pete,
thanks for staying with this - here's the next attempt in being concise ;-)

This code is exactly the same sequence as in the one we discussed earlier,
interestingly this one works as one would expect - the event is raised in a
different thread, preventing the "self-deadlock".

The main difference seems to be - as far as I can tell - that the event is
appearing with its own thread idea and the main thread thus regains control
and continues (after the sleep() in ShellKill()). Now I have no idea how to
influence that nor do I understand why the event in my code is being raised
differently. I also tried the below code with the actual ssh etc code my
realworld app is using - but can not reproduce it either. You saw the process
ID.

Here's the console output where you can see that the main thread (ID10) is
actually continuing (back from sleep) while the event handler sits on the
lock.

-----------

Press CR to start kill shell process

ShellKill - Thread 10
ShellKill - Got lock - go for kill
ShellKill - CMD killed - wait here for event to happen
OnExited - Thread 13 - go for lock
ShellKill - back from sleep - releasing lock
OnExited - inside lock
OnExited - released lock
ShellKill - lock released
Press CR to exit

----------------------------

using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Threading;

namespace Deadlock
{
class Deadlock
{
static Deadlock d = null;
static Process p;
static void Main(string[] args)
{
d = new Deadlock();
d.ShellStart();
Console.WriteLine("Press CR to start kill shell process");
Console.ReadLine();
d.ShellKill();
Console.WriteLine("Press CR to exit");
Console.ReadLine();
}

Semaphore s = new Semaphore(1,1);

private void ShellKill()
{
Console.WriteLine("ShellKill - Thread " +
Thread.CurrentThread.ManagedThreadId);
s.WaitOne();
Console.WriteLine("ShellKill - Got lock - go for kill");
if (p == null)
{
Console.WriteLine("ShellKill : Shell exited already");
}
else
{
//this is the ultimate abbreviation for simplicity of
demonstartion
//in my other code a .NET remote call to a remote process is
causing the
//remote side to exit - which will in sequence release CMD
(or ssh)
p.Kill();
Console.WriteLine("ShellKill - CMD killed - wait here for
event to happen");
//simulate some other cleanup work
Thread.Sleep(2000);
p = null;
Console.WriteLine("ShellKill - back from sleep - releasing
lock");
}
s.Release();
Console.WriteLine("ShellKill - lock released");
}

private void ShellStart()
{
string arg = "/c pause";
ProcessStartInfo psi = new ProcessStartInfo("cmd", arg);
p = new Process();
p.StartInfo = psi;
p.Exited += new EventHandler(OnExited);
p.EnableRaisingEvents = true;
p.Start();
}

void OnExited(object sender, EventArgs e)
{
Console.WriteLine("OnExited - Thread " +
Thread.CurrentThread.ManagedThreadId + " - go for lock");
s.WaitOne();
Console.WriteLine("OnExited - inside lock");
//dispose process (and other resources)
p = null;
s.Release();
Console.WriteLine("OnExited - released lock");
}
}
}
 
oops, too early:

now, going back to my original code the following observation after
comparing differences in code, it gets funny:

case 1:

Before shutting down the remote session I actally disable the process to
send events - after I own the semaphore - because I really wouldn't need them
any more at that point. This however seems not effective (If I do this in the
trial just posted events will be shut down). The execution difference is that
the shell process has actually exited AND a WebException has been thrown from
the termination call into the remote object (that object actually just exits,
which in turn kills the remoting channels). Thereafter - as described - the
event is still raised, under my current thread ID, despite having been
disabled.

case 2:

If I keep the event signaling alive - now the fun part - the event is coming
in, but actually now as expected with its own thread ID - and best: the code
continues where I gave up control after the sleep() expiry.

Here's the updated code to explain; I have so far not provided the server
nor the kill code

using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Threading;
using System.Net;

namespace Deadlock
{
class Deadlock
{
static Deadlock d = null;
static Process p;
static void Main(string[] args)
{
d = new Deadlock();
d.ShellStart();
Console.WriteLine("Press CR to start kill shell process");
Console.ReadLine();
d.ShellKill();
Console.WriteLine("Press CR to exit");
Console.ReadLine();
}

Semaphore s = new Semaphore(1,1);

private void ShellKill()
{
Console.WriteLine("ShellKill - Thread " +
Thread.CurrentThread.ManagedThreadId);
s.WaitOne();
Console.WriteLine("ShellKill - Got lock - go for kill");

//this is the bad guy - here this will work
p.EnableRaisingEvents = false;

if (p == null)
{
Console.WriteLine("ShellKill : Shell exited already");
}
else
{
try
{
//remote invoke exit on the remote server - also causes
shell to exit
// always throws a WebException
}
catch (WebException) { }

if (!p.HasExited) p.Kill();
Console.WriteLine("ShellKill - CMD killed - wait here for
event to happen");
//simulate some other cleanup work
Thread.Sleep(2000);
p = null;
Console.WriteLine("ShellKill - back from sleep - releasing
lock");
}
s.Release();
Console.WriteLine("ShellKill - lock released");
}

private void ShellStart()
{
string arg = @"z1 c:/Programme/AmiBroker/abm/abm.exe -h";
//string arg = "z1 cmd.exe /c pause";
//string arg = "/c pause";
ProcessStartInfo psi = new ProcessStartInfo("ssh", arg);
p = new Process();
p.StartInfo = psi;
p.Exited += new EventHandler(OnExited);
p.EnableRaisingEvents = true;
p.Start();
}

void OnExited(object sender, EventArgs e)
{
Console.WriteLine("OnExited - Thread " +
Thread.CurrentThread.ManagedThreadId + " - go for lock");
s.WaitOne();
Console.WriteLine("OnExited - inside lock");
//dispose process (and other resources)
p = null;
s.Release();
Console.WriteLine("OnExited - released lock");
}
}
}
 
Pete,

now here's the final one:

If you enable the interrupts ( //p.EnableRaisingEvents = false; in line 43 -
ShellKill() ) you will get eventing as expected (separate thredas, nice halt
on semaphore, proper sequencing):

ShellKill - Thread 10
ShellKill - Got lock - go for kill
OnExited - Thread 14 - go for lock
ShellKill - CMD killed - wait here for event to happen
ShellKill - back from sleep - releasing lock
OnExited - inside lock
OnExited - released lock
ShellKill - lock released
Press CR to exit

If you enable this line you will surprisingly (undesiredly) still get an
event firing (hmm??), but the event will be in the same thread context and
logically deadlock itself.

Now I am curious what the real issue is!

Thanks again for sticking with this!

br
Theo

------

using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Threading;
using System.Net;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting;
using System.Collections;

namespace Deadlock
{
class Deadlock : MarshalByRefObject
{
static Deadlock d = null;
static Process p;
static int Main(string[] args)
{
d = new Deadlock();

if (args.Length == 1 && args[0] == "-h") return d.RunServer();

d.ShellStart();
Console.WriteLine("Press CR to start kill shell process");
Console.ReadLine();
d.ShellKill();
Console.WriteLine("Press CR to exit");
Console.ReadLine();

return 0;
}

Semaphore s = new Semaphore(1, 1);
HttpChannel httpchnl = null;
private void ShellKill()
{
Console.WriteLine("ShellKill - Thread " +
Thread.CurrentThread.ManagedThreadId);
s.WaitOne();
Console.WriteLine("ShellKill - Got lock - go for kill");

//comment line out and this will work
p.EnableRaisingEvents = false;

if (p == null)
{
Console.WriteLine("ShellKill : Shell exited already");
}
else
{
try
{
if ((httpchnl =
(HttpChannel)ChannelServices.GetChannel("http")) == null)
{
IDictionary props = new Hashtable();
props["timeout"] = "10000"; //in ms
httpchnl = new HttpChannel(
props,
new SoapClientFormatterSinkProvider(),
null
);
ChannelServices.RegisterChannel(httpchnl, false);
}

//remote invoke exit on the remote server - also causes
shell to exit
Deadlock d = (Deadlock)Activator.GetObject(
typeof(Deadlock),
@"http://localhost:8085/dl");
d.ShutdownServer();
}
catch (WebException) { }

if (!p.HasExited) p.Kill();
Console.WriteLine("ShellKill - CMD killed - wait here for
event to happen");
//simulate some other cleanup work
Thread.Sleep(2000);
p = null;
Console.WriteLine("ShellKill - back from sleep - releasing
lock");
}
s.Release();
Console.WriteLine("ShellKill - lock released");
}

private void ShellStart()
{
//string arg = @"z1 c:/Programme/AmiBroker/abm/abm.exe -h";
string arg = "cmd /c Deadlock.exe -h";
//string arg = "z1 cmd.exe /c pause";
//string arg = "/c pause";
ProcessStartInfo psi = new ProcessStartInfo("cmd", arg);
p = new Process();
p.StartInfo = psi;
p.Exited += new EventHandler(OnExited);
p.EnableRaisingEvents = true;
p.Start();
}

void OnExited(object sender, EventArgs e)
{
Console.WriteLine("OnExited - Thread " +
Thread.CurrentThread.ManagedThreadId + " - go for lock");
s.WaitOne();
Console.WriteLine("OnExited - inside lock");
//dispose process (and other resources)
p = null;
s.Release();
Console.WriteLine("OnExited - released lock");
}

private HttpServerChannel httpserverchannel = null;
private static Thread server = null;
public int RunServer()
{
server = Thread.CurrentThread;
Console.WriteLine("Server Channel registration");
httpserverchannel = new HttpServerChannel(8085);
ChannelServices.RegisterChannel(httpserverchannel, false);
//Register TYPE:
Console.WriteLine("Type registration");
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(Deadlock),
"dl",
WellKnownObjectMode.Singleton
);

try
{
Console.WriteLine("Server started and listening on
http(8085).");
Console.WriteLine("Main Server ThreadId " +
Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("Start time: {0}",
System.DateTime.Now.ToString());
Console.WriteLine("Server " + System.Environment.MachineName
+ " running");
Thread.Sleep(Timeout.Infinite);
}
catch (Exception e)
{
Console.WriteLine("Server released by Exception");
Console.WriteLine(e.Message);
}
Console.WriteLine("\nEnd time: {0}",
System.DateTime.Now.ToString());
Thread.Sleep(5000);
return 0;
}

public int ShutdownServer()
{
Console.WriteLine("Shutdown signal received from client");
server.Interrupt();
server.Join();
return 0;
}
}
}
 
Pete,

now here's the final one:

If you enable the interrupts ( //p.EnableRaisingEvents = false; in line 43 -
ShellKill() ) you will get eventing as expected (separate thredas, nice halt
on semaphore, proper sequencing):

ShellKill - Thread 10
ShellKill - Got lock - go for kill
OnExited - Thread 14 - go for lock
ShellKill - CMD killed - wait here for event to happen
ShellKill - back from sleep - releasing lock
OnExited - inside lock
OnExited - released lock
ShellKill - lock released
Press CR to exit

If you enable this line you will surprisingly (undesiredly) still get an
event firing (hmm??), but the event will be in the same thread context and
logically deadlock itself.

Now I am curious what the real issue is!

I don't really know for sure. However, the behavior you're seeing
could be consistent with a basic implementation detail of the Process
class, if not an actual problem. Specifically, I'm pretty sure that
raising the exit event requires at least a couple of things: a
lower-level native OS callback hooked into the process itself, and the
higher-level .NET mechanism that raises the event.

It's entirely possible that when you set EnableRaisingEvents back to
false, the lower-level callback cannot be disabled, or at least has
been failed to be disabled. If at the same time, the .NET part of the
code is actually shut down, it may be that the native OS callback,
having to be called on _some_ thread, winds up in your main thread
instead of the thread that .NET would have dedicated (or at least
coincidentally made available) for the purpose.

Whether this would be considered "by design" or a flaw in the Process
class, I can't answer. I mean, obviously in your own context you would
prefer that the event not be raised at all, and/or that it always be
raised in a particular thread (or at least not in your main thread).
So presumably you yourself might consider it a flaw. But that doesn't
mean Microsoft would consider it a flaw.

This is especially true given that there's at least two good work-arounds:

1) Don't reset the EnableRaisingEvents property. It may be that
like the things in the StartInfo property, this is one of those "set
before you start the process" things, and you're not supposed to touch
it afterwards. Presumably this would ensure that the exit event is
always raised on a different thread, preventing the deadlock from
happening.

2) Deal with the case where the event is raised in the same thread
that already is trying to shut down the process by setting a flag
indicating that you don't want to run your exit event handling. Since
you're nominally trying to disable the event anyway, this should be
okay with your design, right? Rather than relying on the Process class
to disable the event, just set the flag and return immediately in your
event handler if it's set, rather than trying to get the lock and doing
some work.

For what it's worth, if you're still curious about the mechanism by
which the event is raised on the same thread, you might look at this:
http://msdn2.microsoft.com/en-us/library/ms681951.aspx

It describes the APC mechanism by which a thread in an "alertable wait
state" might wind up running some code while you thought the thread was
asleep.

Pete
 
Pete,

Thanks for your quick reply. As far as I am concerned: of cause it's a flaw
(we used to call them bugs ages ago ;-) ), when times were less politically
correct - especially as the bahaviour is inconsitent.

I guess however we isolated the circumstances. The funny thing is that you
can switch the event signaling on and off - only after the remote call with a
web exception thrown, the event will still pop up, so something inside must
go broke.

Now, with the event popping up in my main thread context I consider a really
bad habit.

The workaround you suggested was what I actually called "homebuilt
semaphore" approach earlier. Because you still have to synchronize setting
and clearing that bool variable which causes the (erroneous) event to just
bypass - you could otherwise be in the shutdown code while due to other
reasons the shell exits.

Well - workaround found for now, maybe you can pass this issue onwards to
some place that would take care for the future benefit of others?

Thanks again for your help, truly appreciated!

Theo
 
Pete:

One addition: I just tried this with several hosts - even though the event
firing is on now. events keep coming up in the main thread context...

Just to let you know
br
Theo
 
[...]
Well - workaround found for now, maybe you can pass this issue onwards to
some place that would take care for the future benefit of others?

To whom would I "pass this issue onwards"?

If you want to report this to Microsoft, you need to do that yourself.
I don't work for Microsoft, nor am I in any way part of their support
staff. Just FYI.

Pete
 
Peter Duniho said:
[...]
Well - workaround found for now, maybe you can pass this issue onwards to
some place that would take care for the future benefit of others?

To whom would I "pass this issue onwards"?

If you want to report this to Microsoft, you need to do that yourself.
I don't work for Microsoft, nor am I in any way part of their support
staff. Just FYI.

Pete
 
Pete,

Somehow my last night post was empty - maybe it was too late, so here I try
again:

I was under the assumption this website was run by microsoft - sorry for the
confusion. Plus your message timestamps looked as if you were west coast US
(I'm in Europe) begin and end of workday ;-). Anyways, no offense intended!

I found the MS bug entry system and filed the topic here:

http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=309324

Let's see what they come up with for an explanation.

Best regards

Theo
 
[...]
I was under the assumption this website was run by microsoft - sorry for the
confusion. Plus your message timestamps looked as if you were west coast US
(I'm in Europe) begin and end of workday ;-). Anyways, no offense intended!

None taken. I just wanted to clarify my relationship (or rather, lack
thereof) with Microsoft.

You are accessing a Microsoft-specific Usenet newsgroup via their
website portal, but it's a public newsgroup carried by a number of
Usenet news services (i.e. not just Microsoft servers) and answers are
frequently provided by people who simply have an interest in .NET
programming. To confuse matters, this newsgroup is _also_ used as a
forum for support to paid MSDN subscribers, and indeed Microsoft
employees can be found here answering those questions.

So your confusion is understandable.
I found the MS bug entry system and filed the topic here:

http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=309324
Let's

see what they come up with for an explanation.

That's great...looks like someone's already performed the first step of
filtering the reports. I have a suspicion the bug report will be
redirected to a category that's more .NET-specific. Your submission
was under the Visual Studio category, but it's not really a VS issue
per se. But in any case, hopefully it will eventually find its way to
the right person and get answered. :)

Thank you for pasting the link here to the bug report. That will make
it much easier for someone who may run into the same problem to find
whatever resolution happens.

Pete
 
Back
Top