completion port thread is leaking when client terminates the connection - asp.net-web-api2

I have a ASP.NET WebApi self-hosted application. HttpSelfHostConfiguration is configured as below
HttpSelfHostConfiguration config = new HttpSelfHostConfiguration("http://0.0.0.0:54781")
{
TransferMode = TransferMode.StreamedResponse,
MaxConcurrentRequests = 1000,
SendTimeout = TimeSpan.FromMinutes(60),
};
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "{controller}",
defaults: new { #controller = "Default" }
);
HttpSelfHostServer server = new HttpSelfHostServer(config);
server.OpenAsync().Wait();
for (;;)
{
int workerThreads, completionPortThreads;
ThreadPool.GetAvailableThreads(out workerThreads, out completionPortThreads);
Console.WriteLine("Available Completion Port Threads = {0};", completionPortThreads);
Console.Out.Flush();
Thread.Sleep(2000);
}
There is an action accepts HTTP GET request as below
public class DefaultController : ApiController
{
public HttpResponseMessage GET()
{
Console.WriteLine("Receive HTTP GET request");
Func<Stream, HttpContent, TransportContext, Task> func = (stream, httpContent, transportContext) =>
{
return Monitor(httpContent, stream);
};
HttpResponseMessage response = Request.CreateResponse();
response.StatusCode = HttpStatusCode.OK;
response.Content = new PushStreamContent(func, new MediaTypeHeaderValue("text/plain"));
return response;
}
async Task Monitor(HttpContent httpContent, Stream stream)
{
try
{
using (StreamWriter sw = new StreamWriter(stream, new UTF8Encoding(false)))
{
for (;;)
{
sw.WriteLine(Guid.NewGuid().ToString());
sw.Flush();
await Task.Delay(1000);
}
}
}
catch (CommunicationException ce)
{
HttpListenerException ex = ce.InnerException as HttpListenerException;
if (ex != null)
{
Console.WriteLine("HttpListenerException occurs, error code = {0}", ex.ErrorCode);
}
else
{
Console.WriteLine("{0} occurs : {1}", ce.GetType().Name, ce.Message);
}
}
catch (Exception ex)
{
Console.WriteLine("{0} occurs : {1}", ex.GetType().Name, ex.Message);
}
finally
{
stream.Close();
stream.Dispose();
httpContent.Dispose();
Console.WriteLine("Dispose");
}
}
}
Open the url http://127.0.0.1:54781/ and I see progressive response comming.
But If the client terminates the connection when the server is sending the response, the completion port thread is taken up and never released.
It able to bring down the application by exhausting the completion port threads pool.

After changing to OWIN self-hosting, this problem disappears. It seems a bug in System.Web.Http.SelfHost
Here is updated code:
class Program
{
static void Main(string[] args)
{
var server = WebApp.Start<Startup>(url: "http://*:54781");
for (;;)
{
int workerThreads, completionPortThreads;
ThreadPool.GetAvailableThreads(out workerThreads, out completionPortThreads);
Console.WriteLine("Worker Threads = {0}; Available Completion Port Threads = {1};", workerThreads, completionPortThreads);
Console.Out.Flush();
Thread.Sleep(2000);
}
}
}
public class Startup
{
// This code configures Web API. The Startup class is specified as a type
// parameter in the WebApp.Start method.
public void Configuration(IAppBuilder appBuilder)
{
// Configure Web API for self-host.
HttpConfiguration config = new HttpConfiguration();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "{controller}",
defaults: new { #controller = "Default" }
);
appBuilder.UseWebApi(config);
}
}

For others coming to this post there is a similar issue in System.Web.Http.SelfHost which by default uses CPU Cores * 100 threads which is the same as the default ThreadPool limit ending in a deadlock as Mono pre-creates all the threads.
The easiest way to get around this is to set the MONO_THREADS_PER_CPU environment variable to a value greater than 1 or use the --server flag when running the Mono application.

Related

NoInitialContextException in CXF Local Transport for testing the JAX-RS

I am following this tutorial: https://cwiki.apache.org/confluence/display/CXF20DOC/JAXRS+Testing
But I get this error:
javax.naming.NoInitialContextException:Need to specify class name in environment or system property, or as an applet parameter, or in an application resource file: java.naming.factory.initial
This is my local server class:
public class CXFLocalTransportTestSuite {
public static final Logger LOGGER = LogManager.getLogger();
public static final String ENDPOINT_ADDRESS = "local://service0";
private static Server server;
#BeforeClass
public static void initialize() throws Exception {
startServer();
}
private static void startServer() throws Exception {
JAXRSServerFactoryBean factory = new JAXRSServerFactoryBean();
factory.setAddress(ENDPOINT_ADDRESS);
List<Class<?>> resourceClasses = new ArrayList<Class<?>>();
resourceClasses.add(CommunicationWSRESTImpl.class);
factory.setResourceClasses(resourceClasses);
List<ResourceProvider> resourceProviders = new ArrayList<>();
resourceProviders.add(new SingletonResourceProvider(new CommunicationWSRESTImpl()));
factory.setResourceProviders(resourceProviders);
List<Object> providers = new ArrayList<Object>();
providers.add(new JacksonJaxbJsonProvider());
providers.add(new ApiOriginFilter());
providers.add(new AuthenticationFilter());
providers.add(new AuthorizationFilter());
factory.setProviders(providers);
server = factory.create();
server.start();
LOGGER.info("LOCAL TRANSPORT STARTED");
}
#AfterClass
public static void destroy() throws Exception {
server.stop();
server.destroy();
LOGGER.info("LOCAL TRANSPORT STOPPED");
}
}
And a client example:
public class CommunicationApiTest {
// [PUBLIC PROFILE]
// --------------------------------------------------------------------------------------------------------
#Test
public void getLinkedComponentsTest() {
// PATH. PARAM.
// ********************************************************************************************************
String userId = "1";
String componentInstance = "a3449197-cc72-49eb-bc14-5d43a80dfa80";
String portId = "00";
// ********************************************************************************************************
WebClient client = WebClient.create(CXFLocalTransportTestSuite.ENDPOINT_ADDRESS);
client.path("/communication/getLinkedComponents/{userId}-{componentInstance}-{portId}", userId, componentInstance, portId);
client.header("Authorization", "Bearer " + CXFLocalTransportTestSuite.authenticationTokenPublicProfile);
Response res = client.get();
if (null != res) {
assertEquals(StatusCode.SUCCESSFUL_OPERATION.getStatusCode(), res.getStatus());
assertNotNull(res.getEntity());
// VALID RESPONSE
// ********************************************************************************************************
assertEquals("> Modules has not been initialized for userID = 1", res.readEntity(GetLinksResult.class).getMessage());
// ********************************************************************************************************
}
}
}
Finally, this is the jax-rs implementation on the server side:
#Path("/communication")
public class CommunicationWSRESTImpl implements CommunicationWS {
#Path("/getLinkedComponents/{userId}-{componentInstance}-{portId}")
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response getLinkedComponents(
#HeaderParam("Authorization") String accessToken,
#PathParam("userId") String userId,
#PathParam("componentInstance") String componentInstance,
#PathParam("portId") String portId) {
LOGGER.info("[CommunicationWSREST - getLinksComponents] userId: " + userId + " -- componentInstace: "
+ componentInstance + " -- portId: " + portId);
GetLinksResult result = new GetLinksResult();
result.setGotten(false);
result.setPortList(null);
if (userId != null && userId.compareTo("") != 0) {
if (componentInstance != null && componentInstance.compareTo("") != 0) {
if (portId != null && portId.compareTo("") != 0) {
TMM tmm = null;
javax.naming.Context initialContext;
try {
initialContext = new InitialContext();
tmm = (TMM) initialContext.lookup("java:app/cos/TMM");
result = tmm.calculateConnectedPorts(userId, componentInstance, portId);
} catch (Exception e) {
LOGGER.error(e);
result.setMessage("> Internal Server Error");
return Response.status(Status.INTERNAL_SERVER_ERROR).entity(result).build();
}
} else {
LOGGER.error("Not found or Empty Port Error");
result.setMessage("> Not found or Empty Port Error");
return Response.status(Status.NOT_FOUND).entity(result).build();
}
} else {
LOGGER.error("Not found or Empty Component Instance Error");
result.setMessage("> Not found or Empty Component Instance Error");
return Response.status(Status.NOT_FOUND).entity(result).build();
}
} else {
LOGGER.error("Not found or Empty userid Error");
result.setMessage("> Not found or Empty username Error");
return Response.status(Status.NOT_FOUND).entity(result).build();
}
return Response.ok(result).build();
}
}
Maybe the problem is the local transport is not correctly configured what launches the exception because of the lookup (see: server side):
TMM tmm = null;
javax.naming.Context initialContext;
try {
initialContext = new InitialContext();
tmm = (TMM) initialContext.lookup("java:app/cos/TMM");
result = tmm.calculateConnectedPorts(userId, componentInstance, portId);
} catch (Exception e) {
..
The problem is most likely because you are running your test in a Java SE environment that is not configured with a JNDI server. If you run your test as part of a WAR inside a Java EE app server, this would probably work just fine.
So you might need to either run your unit test inside an app server or you could try mocking a JNDI server like what is described here: http://en.newinstance.it/2009/03/27/mocking-jndi/#
Hope this helps,
Andy

How to add windows credentials to an apache camel route?

I need to authenticate myself when I want to access a REST api.
I have created a simple example with apache's WinHttpClients which works and also accepts a self signed crt which is used by that site.
These are my dependencies
dependencies {
compile 'org.apache.httpcomponents:httpclient:4.5.+'
compile 'org.apache.httpcomponents:httpclient-win:4.5.+'
testCompile group: 'junit', name: 'junit', version: '4.11'
}
And this is the working code (authorization works, acceptance of crt works)
public class Application {
public static void main(String[] args) throws IOException {
if (WinHttpClients.isWinAuthAvailable()) {
PoolingHttpClientConnectionManager httpClientConnectionManager = new PoolingHttpClientConnectionManager(
buildSSLSocketFactory());
HttpClientBuilder clientBuilder = WinHttpClients.custom().useSystemProperties();
clientBuilder.setConnectionManager(httpClientConnectionManager);
CloseableHttpClient httpClient = clientBuilder.build();
HttpHost httpHost = new HttpHost("server.evilcorp.com", 443, "https");
HttpGet httpGet = new HttpGet(
"/evilwebapi/streams/endpointalpha/data");
httpGet.setHeader("accept", "application/json");
CloseableHttpResponse httpResponse = httpClient.execute(httpHost, httpGet);
String content = EntityUtils.toString(httpResponse.getEntity());
System.out.println(content); // returns expected json result
}
}
private static Registry<ConnectionSocketFactory> buildSSLSocketFactory() {
SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(buildSSLContext(), NoopHostnameVerifier.INSTANCE);
return RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslSocketFactory)
.build();
}
private static SSLContext buildSSLContext() {
SSLContext sslContext = null;
try {
sslContext = new SSLContextBuilder().loadTrustMaterial(null, (TrustStrategy) (arg0, arg1) -> true).build();
} catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
System.out.println("Failed to initialize SSL handling.\n" + e);
}
return sslContext;
}
}
When I try to access the same site through apache camel I get a 401 status.
I tried to configure camel's httpComponent in various ways but so far I can't make authentication work. This is the current camel setup.
These are my dependencies:
dependencies {
compile 'org.apache.camel:camel-core:2.18.+'
compile 'org.apache.camel:camel-sql:2.18.+'
compile 'org.apache.camel:camel-http4:2.18.+'
compile 'org.apache.camel:camel-jetty:2.18.+'
compile 'org.apache.camel:camel-jackson:2.18.+'
compile 'org.apache.camel:camel-guava-eventbus:2.18.+'
compile 'org.apache.camel:camel-quartz2:2.18.+'
compile 'com.fasterxml.jackson.core:jackson-core:2.7.+'
compile 'org.apache.httpcomponents:httpclient:4.5.+'
compile 'org.apache.httpcomponents:httpclient-win:4.5.+'
testRuntime files('src/test/resources')
runtime files('src/main/resources')
}
And this is the RouteBuilder which does not work (authorization doesm't works, statusCode: 401)
context = new DefaultCamelContext(registry);
PropertiesComponent pc = new PropertiesComponent();
pc.setLocation("classpath:model.properties");
context.addComponent("properties", pc);
try {
context.addRoutes(new RouteBuilder() {
public void configure() {
HttpComponent httpComponent = getContext().getComponent("https4", HttpComponent.class);
httpComponent.setHttpClientConfigurer(new WinHttpClientConfigurer());
httpComponent.setClientConnectionManager(new PoolingHttpClientConnectionManager(WinHttpClientConfigurer.buildSSLSocketFactory()));
httpComponent.setHttpConfiguration(buildHttpConfiguration());
getContext().getProperties().put("CamelJacksonEnableTypeConverter", "true");
getContext().getProperties().put("CamelJacksonTypeConverterToPojo", "true");
from("quartz2://pipull?cron=0+0/1+*+1/1+*+?+*")
.setHeader(Exchange.HTTP_QUERY,
simple("start='${header.start}'&end='${header.end}'"))
.multicast().parallelProcessing()
.to("direct:model");
from("direct:model")
.setHeader("contractRef", simple("${properties:model.name}"))
.to("https4://server.evilcorp.com/evilwebapi/streams/endpointalpha/data")
.to("direct:transform");
from("direct:transform").unmarshal()
.json(JsonLibrary.Jackson, Model.class)
.bean(ProcessorImpl.class)
.to("guava-eventbus:botBus");
}
private HttpConfiguration buildHttpConfiguration() {
WindowsCredentialsProvider credentialsProvider = new WindowsCredentialsProvider(
new SystemDefaultCredentialsProvider());
Credentials credentials = credentialsProvider.getCredentials(new AuthScope(null, -1, null, AuthSchemes.NTLM));
HttpConfiguration httpConfiguration = new HttpConfiguration();
httpConfiguration.setAuthMethod(AuthSchemes.NTLM);
httpConfiguration.setAuthUsername(credentials.getUserPrincipal().getName());
return httpConfiguration;
}
});
context.start();
} catch (Exception e) {
isRunning.set(false);
throw new RuntimeException(e);
}
I have resolved the problem through subtyping HttpComponent and adding that to the camel context.
public class WinHttpComponent extends HttpComponent {
private static final Logger LOG = LoggerFactory.getLogger(WinHttpComponent.class);
public WinHttpComponent() {
this(HttpEndpoint.class);
}
public WinHttpComponent(Class<? extends HttpEndpoint> endpointClass) {
super(endpointClass);
}
#Override protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception {
// copy-paste everything from super method
// replace this
// HttpClientBuilder clientBuilder = HttpClientBuilder.create();
// with this
HttpClientBuilder clientBuilder = WinHttpClients.custom().useSystemProperties();
// copy-paste everything from super method
}
}
context = new DefaultCamelContext(registry);
context.addComponent("https4", new WinHttpComponent());
try {
context.addRoutes(new RouteBuilder() {
public void configure() {
HttpComponent httpComponent = getContext().getComponent("https4", HttpComponent.class);
// connection manager which accepts self-signed cert
httpComponent.setClientConnectionManager(new PoolingHttpClientConnectionManager(
NoopSslVerifierHttpClientConfigurer.buildSSLSocketFactory()));
...
...
...
}
}

Callback is not invoked on the client

I have a self-hosted service that processes long running jobs submitted by a client over net.tcp binding. While the job is running (within a Task), the service will push status updates to the client via a one-way callback. This works fine, however when I attempt to invoke another callback to notify the client the job has completed (also one-way), the callback is never received/invoked on the client. I do not receive any exceptions in this process.
My Callback contract looks like this:
public interface IWorkflowCallback
{
[OperationContract(IsOneWay = true)]
[ApplySharedTypeResolverAttribute]
void UpdateStatus(WorkflowJobStatusUpdate StatusUpdate);
[OperationContract(IsOneWay = true)]
[ApplySharedTypeResolverAttribute]
void NotifyJobCompleted(WorkflowJobCompletionNotice Notice);
}
Code from the service that invokes the callbacks: (not in the service implementation itself, but called directly from the service implementation)
public WorkflowJobTicket AddToQueue(WorkflowJobRequest Request)
{
if (this.workflowEngine.WorkerPoolFull)
{
throw new QueueFullException();
}
var user = ServiceUserManager.CurrentUser;
var context = OperationContext.Current;
var workerId = this.workflowEngine.RunWorkflowJob(user, Request, new Object[]{new DialogServiceExtension(context)});
var workerjob = this.workflowEngine.FindJob(workerId);
var ticket = new WorkflowJobTicket()
{
JobRequestId = Request.JobRequestId,
JobTicketId = workerId
};
user.RegisterTicket<IWorkflowCallback>(ticket);
workerjob.WorkflowJobCompleted += this.NotifyJobComplete;
workerjob.Status.PropertyChanged += this.NotifyJobStatusUpdate;
this.notifyQueueChanged();
return ticket;
}
protected void NotifyJobStatusUpdate(object sender, PropertyChangedEventArgs e)
{
var user = ServiceUserManager.GetInstance().GetUserWithTicket((sender as WorkflowJobStatus).JobId);
Action<IWorkflowCallback> action = (callback) =>
{
ICommunicationObject communicationCallback = (ICommunicationObject)callback;
if (communicationCallback.State == CommunicationState.Opened)
{
try
{
var updates = (sender as WorkflowJobStatus).GetUpdates();
callback.UpdateStatus(updates);
}
catch (Exception)
{
communicationCallback.Abort();
}
}
};
user.Invoke<IWorkflowCallback>(action);
}
protected void NotifyJobComplete(WorkflowJob job, EventArgs e)
{
var user = ServiceUserManager.GetInstance().GetUserWithTicket(job.JobId);
Action<IWorkflowCallback> action = (callback) =>
{
ICommunicationObject communicationCallback = (ICommunicationObject)callback;
if (communicationCallback.State == CommunicationState.Opened)
{
try
{
var notice = new WorkflowJobCompletionNotice()
{
Ticket = user.GetTicket(job.JobId),
RuntimeOptions = job.RuntimeOptions
};
callback.NotifyJobCompleted(notice);
}
catch (Exception)
{
communicationCallback.Abort();
}
}
};
user.Invoke<IWorkflowCallback>(action);
}
In the user.Invoke<IWorkflowCallback>(action) method, the Action is passed an instance of the callback channel via OperationContext.GetCallbackChannel<IWorkflowCallback>().
I can see that the task that invokes the job completion notice is executed by the the service, yet I do not receive the call on the client end. Further, the update callback is able to be invoked successfully after a completion notice is sent, so it does not appear that the channel is quietly faulting.
Any idea why, out of these two callbacks that are implemented almost identically, only one works?
Thanks in advance for any insight.

Maximum threads issue

To begin with, I checked the discussions regarding this issue and couldn't find an answer to my problem and that's why I'm opening this question.
I've set up a web service using restlet 2.0.15.The implementation is only for the server. The connections to the server are made through a webpage, and therefore I didn't use ClientResource.
Most of the answers to the exhaustion of the thread pool problem suggested the inclusion of
#exhaust + #release
The process of web service can be described as a single function.Receive GET requests from the webpage, query the database, frame the results in XML and return the final representation. I used a Filter to override the beforeHandle and afterHandle.
The code for component creation code:
Component component = new Component();
component.getServers().add(Protocol.HTTP, 8188);
component.getContext().getParameters().add("maxThreads", "512");
component.getContext().getParameters().add("minThreads", "100");
component.getContext().getParameters().add("lowThreads", "145");
component.getContext().getParameters().add("maxQueued", "100");
component.getContext().getParameters().add("maxTotalConnections", "100");
component.getContext().getParameters().add("maxIoIdleTimeMs", "100");
component.getDefaultHost().attach("/orcamento2013", new ServerApp());
component.start();
The parameters are the result of a discussion present in this forum and modification by my part in an attempt to maximize efficiency.
Coming to the Application, the code is as follows:
#Override
public synchronized Restlet createInboundRoot() {
// Create a router Restlet that routes each call to a
// new instance of HelloWorldResource.
Router router = new Router(getContext());
// Defines only one route
router.attach("/{taxes}", ServerImpl.class);
//router.attach("/acores/{taxes}", ServerImplAcores.class);
System.out.println(router.getRoutes().size());
OriginFilter originFilter = new OriginFilter(getContext());
originFilter.setNext(router);
return originFilter;
}
I used an example Filter found in a discussion here, too. The implementation is as follows:
public OriginFilter(Context context) {
super(context);
}
#Override
protected int beforeHandle(Request request, Response response) {
if (Method.OPTIONS.equals(request.getMethod())) {
Form requestHeaders = (Form) request.getAttributes().get("org.restlet.http.headers");
String origin = requestHeaders.getFirstValue("Origin", true);
Form responseHeaders = (Form) response.getAttributes().get("org.restlet.http.headers");
if (responseHeaders == null) {
responseHeaders = new Form();
response.getAttributes().put("org.restlet.http.headers", responseHeaders);
responseHeaders.add("Access-Control-Allow-Origin", origin);
responseHeaders.add("Access-Control-Allow-Methods", "GET,POST,DELETE");
responseHeaders.add("Access-Control-Allow-Headers", "Content-Type");
responseHeaders.add("Access-Control-Allow-Credentials", "true");
response.setEntity(new EmptyRepresentation());
return SKIP;
}
}
return super.beforeHandle(request, response);
}
#Override
protected void afterHandle(Request request, Response response) {
if (!Method.OPTIONS.equals(request.getMethod())) {
Form requestHeaders = (Form) request.getAttributes().get("org.restlet.http.headers");
String origin = requestHeaders.getFirstValue("Origin", true);
Form responseHeaders = (Form) response.getAttributes().get("org.restlet.http.headers");
if (responseHeaders == null) {
responseHeaders = new Form();
response.getAttributes().put("org.restlet.http.headers", responseHeaders);
responseHeaders.add("Access-Control-Allow-Origin", origin);
responseHeaders.add("Access-Control-Allow-Methods", "GET,POST,DELETE"); //
responseHeaders.add("Access-Control-Allow-Headers", "Content-Type");
responseHeaders.add("Access-Control-Allow-Credentials", "true");
}
}
super.afterHandle(request, response);
Representation requestRepresentation = request.getEntity();
if (requestRepresentation != null) {
try {
requestRepresentation.exhaust();
} catch (IOException e) {
// handle exception
}
requestRepresentation.release();
}
Representation responseRepresentation = response.getEntity();
if(responseRepresentation != null) {
try {
responseRepresentation.exhaust();
} catch (IOException ex) {
Logger.getLogger(OriginFilter.class.getName()).log(Level.SEVERE, null, ex);
} finally {
}
}
}
The responseRepresentation does not have a #release method because it crashes the processes giving the warning WARNING: A response with a 200 (Ok) status should have an entity (...)
The code of the ServerResource implementation is the following:
public class ServerImpl extends ServerResource {
String itemName;
#Override
protected void doInit() throws ResourceException {
this.itemName = (String) getRequest().getAttributes().get("taxes");
}
#Get("xml")
public Representation makeItWork() throws SAXException, IOException {
DomRepresentation representation = new DomRepresentation(MediaType.TEXT_XML);
DAL dal = new DAL();
String ip = getRequest().getCurrent().getClientInfo().getAddress();
System.out.println(itemName);
double tax = Double.parseDouble(itemName);
Document myXML = Auxiliar.getMyXML(tax, dal, ip);
myXML.normalizeDocument();
representation.setDocument(myXML);
return representation;
}
#Override
protected void doRelease() throws ResourceException {
super.doRelease();
}
}
I've tried the solutions provided in other threads but none of them seem to work. Firstly, it does not seem that the thread pool is augmented with the parameters set as the warnings state that the thread pool available is 10. As mentioned before, the increase of the maxThreads value only seems to postpone the result.
Example: INFO: Worker service tasks: 0 queued, 10 active, 17 completed, 27 scheduled.
There could be some error concerning the Restlet version, but I downloaded the stable version to verify this was not the issue.The Web Service is having around 5000 requests per day, which is not much.Note: the insertion of the #release method either in the ServerResource or OriginFilter returns error and the referred warning ("WARNING: A response with a 200 (Ok) status should have an entity (...)")
Please guide.
Thanks!
By reading this site the problem residing in the server-side that I described was resolved by upgrading the Restlet distribution to the 2.1 version.
You will need to alter some code. You should consult the respective migration guide.

Duplex WCF + Static Collection of COM objects

I am trying to build a WCF service that exposes the functionality of a particular COM object that I do not have the original source for. I am using duplex binding so that each client has their own instance as there are events tied to each particular instance which are delivered through a callback (IAgent). It appears there is a deadlock or something because after the first action, my service blocks at my second action's lock. I have tried implementing these custom STA attribute and operation behaviors (http://devlicio.us/blogs/scott_seely/archive/2009/07/17/calling-an-sta-com-object-from-a-wcf-operation.aspx) but my OperationContext.Current is always null. Any advice is much appreciated.
Service
Collection:
private static Dictionary<IAgent, COMAgent> agents = new Dictionary<IAgent, COMAgent>();
First action:
public void Login(LoginRequest request)
{
IAgent agent = OperationContext.Current.GetCallbackChannel<IAgent>();
lock (agents)
{
if (agents.ContainsKey(agent))
throw new FaultException("You are already logged in.");
else
{
ICOMClass startup = new ICOMClass();
string server = ConfigurationManager.AppSettings["Server"];
int port = Convert.ToInt32(ConfigurationManager.AppSettings["Port"]);
bool success = startup.Logon(server, port, request.Username, request.Password);
if (!success)
throw new FaultException<COMFault>(new COMFault { ErrorText = "Could not log in." });
COMAgent comAgent = new COMAgent { Connection = startup };
comAgent.SomeEvent += new EventHandler<COMEventArgs>(comAgent_COMEvent);
agents.Add(agent, comAgent);
}
}
}
Second Action:
public void Logoff()
{
IAgent agent = OperationContext.Current.GetCallbackChannel<IAgent>();
lock (agents)
{
COMAgent comAgent = agents[agent];
try
{
bool success = comAgent.Connection.Logoff();
if (!success)
throw new FaultException<COMFault>(new COMFault { ErrorText = "Could not log off." });
agents.Remove(agent);
}
catch (Exception exc)
{
throw new FaultException(exc.Message);
}
}
}
Take a look at this very similar post: http://www.netfxharmonics.com/2009/07/Accessing-WPF-Generated-Images-Via-WCF
You have to use an OperationContextScope to have access to the current OperationContext from the newly generated thread:
System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ThreadStart(delegate
{
using (System.ServiceModel.OperationContextScope scope = new System.ServiceModel.OperationContextScope(context))
{
result = InnerOperationInvoker.Invoke(instance, inputs, out staOutputs);
}
}));