public bool OnJobCalcResult(JobResult jobResult)
        {
            //No retry for failed job. Retry should be occur only when node is unreachable

            JobStatus jobStatus = GetJobStatusByJobID(jobResult.job.jobID);
            //mark job status
            if (jobStatus != null)
            {
                if (jobStatus.jobStatusType != JobStatusType.CANCELED)
                    jobStatus.jobStatusType = JobStatusType.FINISHED;

            }
            else
            {
                Console.WriteLine(DateTime.Now.ToString("[HH:mm:ss.fff]") + "[Error][OnJobCalcResult] No job status found for jobID=" + jobResult.job.jobID);
                return false;
            }

            //notify node for job finish
            //When node is not alive, dont need to pass result to client
            Node node = nodeManager.GetNodeByNodeKey(jobResult.job.nodeKey);
            if (node != null)
            {
                node.WorkerFinish(jobResult.job.jobID);
            }
            else
            {
                Console.WriteLine(DateTime.Now.ToString("[HH:mm:ss.fff]") + "[Error][OnJobCalcResult] No node found for nodeKey=" + jobResult.job.nodeKey + ". Maybe logged out because of hearbeat.");
                return false;
            }
            ProcessWaitingJobQueue();

            bool ret = true;
            Client client = clientManager.GetClientByClientKey(jobResult.job.clientKey);
            if (client != null)
            {
                AsyncServerClientJobCalcResultCastMessage msg = new AsyncServerClientJobCalcResultCastMessage();
                msg.jobResult = jobResult;
                if (!client.GetAsyncComm().SendCast(msg))
                    Console.WriteLine("[Error] SendCast failed!");

                ret = true;
            }
            else
            {
                ret = false;
            }
            if (jobResult.resultString.Trim() == "")
            {
                string str = string.Format("JobCode\n{0}\n--------------------------\nParameter\n{1}\n--------------------------\n{2}\n--------------------------\n", jobResult.job.clientSideJobCode, jobResult.job.parameter, jobResult.resultString);
                //File.WriteAllText("empty_ressult.txt", str);
                File.AppendAllText("empty_ressult.txt", str);
            }
            //additionally add to cache
            jobResultCache.AddJobResultToCache(jobResult);

            return ret;
        }
        private bool ProcessJobWithCache(Job job)
        {
            string cacheKey = job.GetCacheKey();
            string resultString = jobResultCache.GetJobResultStringFromCache(cacheKey);
            if (resultString != null)
            {
                //cache hit
                JobResult jobResult = new JobResult();
                jobResult.job = job;
                jobResult.jobSucceed = true;
                jobResult.resultString = resultString;

                JobStatus jobStatus = GetJobStatusByJobID(job.jobID);
                //mark job status
                if (jobStatus != null)
                {
                    if (jobStatus.jobStatusType != JobStatusType.CANCELED)
                        jobStatus.jobStatusType = JobStatusType.FINISHED;
                }
                else
                {
                    Console.WriteLine(DateTime.Now.ToString("[HH:mm:ss.fff]") + "[Error][OnJobCalcResult] No job status found for jobID=" + job.jobID);
                }
                Client client = clientManager.GetClientByClientKey(jobResult.job.clientKey);
                if (client != null)
                {
                    AsyncServerClientJobCalcResultCastMessage castmsg = new AsyncServerClientJobCalcResultCastMessage();
                    castmsg.jobResult = jobResult;
                    Console.WriteLine("[ProcessJobWithCache] Send job result : " + jobResult.ToString());
                    if (!client.GetAsyncComm().SendCast(castmsg))
                        Console.WriteLine("[Error] SendCast failed!");
                }
                return true;
            }
            return false;
        }