ABAO77 commited on
Commit
2d7b420
·
verified ·
1 Parent(s): 7189a67

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +54 -239
app.py CHANGED
@@ -1,16 +1,21 @@
1
  import os
2
  import tempfile
3
  import shutil
 
4
  import asyncio
5
  from pathlib import Path
6
  from typing import List, Optional, Dict, Any
7
  import uuid
8
  import re
 
 
9
  from fastapi import (
10
  UploadFile,
11
  File,
12
  Form,
13
  HTTPException,
 
 
14
  FastAPI,
15
  )
16
  from fastapi.responses import JSONResponse
@@ -19,9 +24,8 @@ import json
19
  import resource
20
  import platform
21
  from loguru import logger
22
- import subprocess
23
 
24
- app = FastAPI(title="Code Execution and Input Analysis API", docs_url="/")
25
 
26
  # Configuration
27
  MAX_EXECUTION_TIME = 5 # seconds
@@ -63,68 +67,6 @@ class CodeExecutor:
63
  "java": "javac",
64
  "python": "python3",
65
  }
66
- # Check and log ulimits on startup
67
- self._check_ulimits()
68
-
69
- def _check_ulimits(self):
70
- """Check current ulimits and log them"""
71
- try:
72
- # Check various ulimits
73
- limits = {
74
- "RLIMIT_NOFILE": resource.RLIMIT_NOFILE, # file descriptors
75
- "RLIMIT_NPROC": resource.RLIMIT_NPROC, # processes
76
- "RLIMIT_STACK": resource.RLIMIT_STACK, # stack size
77
- }
78
-
79
- logger.info("Current ulimits:")
80
- for name, limit_type in limits.items():
81
- try:
82
- soft, hard = resource.getrlimit(limit_type)
83
- logger.info(f"{name}: soft={soft}, hard={hard}")
84
- except:
85
- pass
86
-
87
- # Try to increase limits if they're too low
88
- try:
89
- # Increase file descriptors
90
- resource.setrlimit(resource.RLIMIT_NOFILE, (65536, 65536))
91
- except:
92
- try:
93
- # Try a lower value if the high one fails
94
- resource.setrlimit(resource.RLIMIT_NOFILE, (4096, 4096))
95
- except:
96
- pass
97
-
98
- except Exception as e:
99
- logger.warning(f"Could not check/set ulimits: {e}")
100
-
101
- async def _create_java_wrapper(self, workspace: str, jvm_opts: List[str]) -> str:
102
- """Create a wrapper script for Java execution with ulimit settings"""
103
-
104
- wrapper_content = f"""#!/bin/bash
105
- # Set ulimits to prevent memory allocation issues
106
- ulimit -n 65536 2>/dev/null || ulimit -n 4096
107
- ulimit -u 32768 2>/dev/null || ulimit -u 2048
108
- ulimit -s 8192 2>/dev/null || ulimit -s 4096
109
- ulimit -v unlimited 2>/dev/null || true
110
- ulimit -m unlimited 2>/dev/null || true
111
-
112
- # Log current ulimits for debugging
113
- echo "Current ulimits:" >&2
114
- ulimit -a >&2
115
-
116
- # Execute Java with the provided options
117
- exec java {' '.join(jvm_opts)} "$@"
118
- """
119
-
120
- wrapper_path = os.path.join(workspace, "java_wrapper.sh")
121
- with open(wrapper_path, 'w') as f:
122
- f.write(wrapper_content)
123
-
124
- # Make executable
125
- os.chmod(wrapper_path, 0o755)
126
-
127
- return wrapper_path
128
 
129
  def set_resource_limits(self):
130
  """Set resource limits for subprocess (Unix only)"""
@@ -134,61 +76,6 @@ exec java {' '.join(jvm_opts)} "$@"
134
  )
135
  resource.setrlimit(resource.RLIMIT_AS, (MAX_MEMORY, MAX_MEMORY))
136
 
137
- def check_available_memory(self) -> int:
138
- """Create a wrapper script for Java execution with ulimit settings"""
139
-
140
- wrapper_content = f"""#!/bin/bash
141
- # Set ulimits to prevent memory allocation issues
142
- ulimit -n 65536 2>/dev/null || ulimit -n 4096
143
- ulimit -u 32768 2>/dev/null || ulimit -u 2048
144
- ulimit -s 8192 2>/dev/null || ulimit -s 4096
145
- ulimit -v unlimited 2>/dev/null || true
146
- ulimit -m unlimited 2>/dev/null || true
147
-
148
- # Log current ulimits for debugging
149
- echo "Current ulimits:" >&2
150
- ulimit -a >&2
151
-
152
- # Execute Java with the provided options
153
- exec java {' '.join(jvm_opts)} "$@"
154
- """
155
-
156
- wrapper_path = os.path.join(workspace, "java_wrapper.sh")
157
- with open(wrapper_path, 'w') as f:
158
- f.write(wrapper_content)
159
-
160
- # Make executable
161
- os.chmod(wrapper_path, 0o755)
162
-
163
- return wrapper_path
164
- """Check available memory in MB"""
165
- try:
166
- with open('/proc/meminfo', 'r') as f:
167
- meminfo = f.read()
168
-
169
- available_match = re.search(r'MemAvailable:\s+(\d+)\s+kB', meminfo)
170
- if available_match:
171
- available_kb = int(available_match.group(1))
172
- available_mb = available_kb // 1024
173
- logger.info(f"Available memory: {available_mb}MB")
174
- return available_mb
175
-
176
- # Fallback to MemFree + Cached
177
- free_match = re.search(r'MemFree:\s+(\d+)\s+kB', meminfo)
178
- cached_match = re.search(r'Cached:\s+(\d+)\s+kB', meminfo)
179
-
180
- if free_match and cached_match:
181
- free_kb = int(free_match.group(1))
182
- cached_kb = int(cached_match.group(1))
183
- available_mb = (free_kb + cached_kb) // 1024
184
- logger.info(f"Estimated available memory: {available_mb}MB")
185
- return available_mb
186
-
187
- except Exception as e:
188
- logger.warning(f"Could not check memory: {e}")
189
-
190
- return 0 # Unknown
191
-
192
  def analyze_input_patterns(
193
  self, language: str, file_contents: Dict[str, str]
194
  ) -> InputAnalysisResult:
@@ -571,28 +458,26 @@ exec java {' '.join(jvm_opts)} "$@"
571
  workspace: str,
572
  input_data: Optional[List[str]] = None,
573
  ) -> ExecutionResult:
574
- """Compile and execute Java code with optimized memory settings"""
575
-
576
  # Check if we have .java files to compile
577
  java_files = list(Path(workspace).glob("*.java"))
578
  needs_compilation = len(java_files) > 0
579
-
580
  # If we have .java files, compile them
581
  if needs_compilation:
582
  logger.info(f"Found {len(java_files)} Java source files, compiling...")
583
-
584
- # Compile with minimal memory
585
- compile_cmd = ["javac", "-J-Xmx32m"] + [str(f) for f in java_files]
586
-
587
  compile_process = await asyncio.create_subprocess_exec(
588
- *compile_cmd,
 
589
  cwd=workspace,
590
  stdout=asyncio.subprocess.PIPE,
591
  stderr=asyncio.subprocess.PIPE,
592
  )
593
-
594
  stdout, stderr = await compile_process.communicate()
595
-
596
  if compile_process.returncode != 0:
597
  return ExecutionResult(
598
  success=False,
@@ -601,20 +486,20 @@ exec java {' '.join(jvm_opts)} "$@"
601
  execution_time=0,
602
  exit_code=compile_process.returncode,
603
  )
604
-
605
  logger.info("Java compilation successful")
606
  else:
607
- # Check if we have .class files
608
  class_files_missing = []
609
  for main_file in main_files:
610
  if main_file.endswith(".class"):
611
  class_file_path = os.path.join(workspace, main_file)
612
  else:
613
  class_file_path = os.path.join(workspace, f"{main_file}.class")
614
-
615
  if not os.path.exists(class_file_path):
616
  class_files_missing.append(main_file)
617
-
618
  if class_files_missing:
619
  return ExecutionResult(
620
  success=False,
@@ -623,10 +508,10 @@ exec java {' '.join(jvm_opts)} "$@"
623
  execution_time=0,
624
  exit_code=-1,
625
  )
626
-
627
  logger.info("Using existing .class files, skipping compilation")
628
-
629
- # Execute Java with multiple fallback strategies
630
  results = []
631
  for main_file in main_files:
632
  # Determine class name
@@ -636,7 +521,7 @@ exec java {' '.join(jvm_opts)} "$@"
636
  class_name = main_file.replace(".java", "")
637
  else:
638
  class_name = main_file
639
-
640
  # Verify the .class file exists
641
  class_file_path = os.path.join(workspace, f"{class_name}.class")
642
  if not os.path.exists(class_file_path):
@@ -650,108 +535,48 @@ exec java {' '.join(jvm_opts)} "$@"
650
  )
651
  )
652
  continue
653
-
654
- # Try different JVM configurations
655
- jvm_configs = [
656
- # Config 1: Ultra minimal with no code cache reservation
657
- [
658
- "-Xint", # Interpreter only mode (no JIT)
659
- "-Xmx32m",
660
- "-Xms2m",
661
- "-XX:MaxMetaspaceSize=16m",
662
- "-Djava.awt.headless=true",
663
- ],
664
- # Config 2: Minimal with small code cache
665
- [
666
- "-XX:+UseSerialGC",
667
- "-Xmx32m",
668
- "-Xms4m",
669
- "-XX:ReservedCodeCacheSize=8m",
670
- "-XX:InitialCodeCacheSize=2m",
671
- "-XX:MaxMetaspaceSize=24m",
672
- "-XX:-TieredCompilation",
673
- "-Djava.awt.headless=true",
674
- ],
675
- # Config 3: Slightly more memory
676
- [
677
- "-Xmx48m",
678
- "-Xms8m",
679
- "-XX:ReservedCodeCacheSize=16m",
680
- "-XX:MaxMetaspaceSize=32m",
681
- "-XX:-TieredCompilation",
682
- "-Djava.awt.headless=true",
683
- ],
684
- # Config 4: Use container support
685
- [
686
- "-XX:+UseContainerSupport",
687
- "-XX:MaxRAMPercentage=25.0",
688
- "-XX:-TieredCompilation",
689
- "-Djava.awt.headless=true",
690
- ],
691
- ]
692
-
693
- result = None
694
- for config_idx, jvm_opts in enumerate(jvm_configs):
695
- try:
696
- logger.info(f"Trying JVM config {config_idx + 1} for {class_name}")
697
- start_time = asyncio.get_event_loop().time()
698
-
699
- # For first two configs, try with wrapper script
700
- if config_idx < 2:
701
- wrapper_path = await self._create_java_wrapper(workspace, jvm_opts)
702
- java_cmd = [wrapper_path, class_name]
703
- else:
704
- java_cmd = ["java"] + jvm_opts + [class_name]
705
-
706
- stdout, stderr, returncode = await self._execute_with_input(
707
- java_cmd, workspace, input_data
708
- )
709
-
710
- execution_time = asyncio.get_event_loop().time() - start_time
711
-
712
- # Check if it's a memory-related error
713
- stderr_text = stderr.decode("utf-8", errors="replace")
714
- if "Could not reserve" in stderr_text or "insufficient memory" in stderr_text:
715
- logger.warning(f"Config {config_idx + 1} failed with memory error, trying next...")
716
- continue
717
-
718
- result = ExecutionResult(
719
  success=returncode == 0,
720
  stdout=stdout.decode("utf-8", errors="replace"),
721
- stderr=stderr_text,
722
  execution_time=execution_time,
723
  exit_code=returncode,
724
  )
725
-
726
- logger.info(f"Java execution completed with config {config_idx + 1}")
727
- break
728
-
729
- except asyncio.TimeoutError:
730
- result = ExecutionResult(
731
  success=False,
732
  stdout="",
733
  stderr="Execution timeout exceeded",
734
  execution_time=MAX_EXECUTION_TIME,
735
  exit_code=-1,
736
  )
737
- break
738
- except Exception as e:
739
- logger.error(f"Error with config {config_idx + 1}: {str(e)}")
740
- continue
741
-
742
- # If all configs failed, return error
743
- if result is None:
744
- result = ExecutionResult(
745
- success=False,
746
- stdout="",
747
- stderr="Failed to execute Java code with all memory configurations. The container environment may have insufficient memory for JVM.",
748
- execution_time=0,
749
- exit_code=-1,
750
- error="All JVM configurations failed"
751
  )
752
-
753
- results.append(result)
754
-
 
 
 
 
 
 
 
 
 
755
  return self._combine_results(results, main_files)
756
 
757
  async def _execute_c_cpp(
@@ -995,12 +820,6 @@ def detect_language_from_files(main_files: List[str]) -> str:
995
  return detected_language
996
 
997
 
998
- @app.get("/health")
999
- async def health_check():
1000
- """Health check endpoint"""
1001
- return {"status": "healthy", "timestamp": asyncio.get_event_loop().time()}
1002
-
1003
-
1004
  @app.post("/analyze-inputs")
1005
  async def analyze_inputs(code_content: str = Form(...), language: str = Form(...)):
1006
  """
@@ -1032,6 +851,8 @@ async def analyze_inputs(code_content: str = Form(...), language: str = Form(...
1032
  )
1033
  result_dict = analysis_result.model_dump()
1034
 
 
 
1035
  logger.info(f"Input analysis completed for {language} code")
1036
  return JSONResponse(content=result_dict)
1037
 
@@ -1223,9 +1044,3 @@ input("Press enter to continue...")
1223
  "", # for press enter
1224
  ],
1225
  }
1226
-
1227
-
1228
- if __name__ == "__main__":
1229
- import uvicorn
1230
-
1231
- uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True)
 
1
  import os
2
  import tempfile
3
  import shutil
4
+ import subprocess
5
  import asyncio
6
  from pathlib import Path
7
  from typing import List, Optional, Dict, Any
8
  import uuid
9
  import re
10
+ import time
11
+ import hashlib
12
  from fastapi import (
13
  UploadFile,
14
  File,
15
  Form,
16
  HTTPException,
17
+ BackgroundTasks,
18
+ APIRouter,
19
  FastAPI,
20
  )
21
  from fastapi.responses import JSONResponse
 
24
  import resource
25
  import platform
26
  from loguru import logger
 
27
 
28
+ app = FastAPI(title="Code Grader API", version="1.0.0", docs_url="/")
29
 
30
  # Configuration
31
  MAX_EXECUTION_TIME = 5 # seconds
 
67
  "java": "javac",
68
  "python": "python3",
69
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
  def set_resource_limits(self):
72
  """Set resource limits for subprocess (Unix only)"""
 
76
  )
77
  resource.setrlimit(resource.RLIMIT_AS, (MAX_MEMORY, MAX_MEMORY))
78
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  def analyze_input_patterns(
80
  self, language: str, file_contents: Dict[str, str]
81
  ) -> InputAnalysisResult:
 
458
  workspace: str,
459
  input_data: Optional[List[str]] = None,
460
  ) -> ExecutionResult:
461
+ """Compile and execute Java code with input support"""
462
+
463
  # Check if we have .java files to compile
464
  java_files = list(Path(workspace).glob("*.java"))
465
  needs_compilation = len(java_files) > 0
466
+
467
  # If we have .java files, compile them
468
  if needs_compilation:
469
  logger.info(f"Found {len(java_files)} Java source files, compiling...")
470
+
 
 
 
471
  compile_process = await asyncio.create_subprocess_exec(
472
+ "javac",
473
+ *[str(f) for f in java_files],
474
  cwd=workspace,
475
  stdout=asyncio.subprocess.PIPE,
476
  stderr=asyncio.subprocess.PIPE,
477
  )
478
+
479
  stdout, stderr = await compile_process.communicate()
480
+
481
  if compile_process.returncode != 0:
482
  return ExecutionResult(
483
  success=False,
 
486
  execution_time=0,
487
  exit_code=compile_process.returncode,
488
  )
489
+
490
  logger.info("Java compilation successful")
491
  else:
492
+ # Check if we have .class files for the main files
493
  class_files_missing = []
494
  for main_file in main_files:
495
  if main_file.endswith(".class"):
496
  class_file_path = os.path.join(workspace, main_file)
497
  else:
498
  class_file_path = os.path.join(workspace, f"{main_file}.class")
499
+
500
  if not os.path.exists(class_file_path):
501
  class_files_missing.append(main_file)
502
+
503
  if class_files_missing:
504
  return ExecutionResult(
505
  success=False,
 
508
  execution_time=0,
509
  exit_code=-1,
510
  )
511
+
512
  logger.info("Using existing .class files, skipping compilation")
513
+
514
+ # Execute main files
515
  results = []
516
  for main_file in main_files:
517
  # Determine class name
 
521
  class_name = main_file.replace(".java", "")
522
  else:
523
  class_name = main_file
524
+
525
  # Verify the .class file exists
526
  class_file_path = os.path.join(workspace, f"{class_name}.class")
527
  if not os.path.exists(class_file_path):
 
535
  )
536
  )
537
  continue
538
+
539
+ try:
540
+ start_time = asyncio.get_event_loop().time()
541
+
542
+ stdout, stderr, returncode = await self._execute_with_input(
543
+ ["java", class_name], workspace, input_data
544
+ )
545
+
546
+ execution_time = asyncio.get_event_loop().time() - start_time
547
+
548
+ results.append(
549
+ ExecutionResult(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
550
  success=returncode == 0,
551
  stdout=stdout.decode("utf-8", errors="replace"),
552
+ stderr=stderr.decode("utf-8", errors="replace"),
553
  execution_time=execution_time,
554
  exit_code=returncode,
555
  )
556
+ )
557
+
558
+ except asyncio.TimeoutError:
559
+ results.append(
560
+ ExecutionResult(
 
561
  success=False,
562
  stdout="",
563
  stderr="Execution timeout exceeded",
564
  execution_time=MAX_EXECUTION_TIME,
565
  exit_code=-1,
566
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
567
  )
568
+ except Exception as e:
569
+ results.append(
570
+ ExecutionResult(
571
+ success=False,
572
+ stdout="",
573
+ stderr=str(e),
574
+ execution_time=0,
575
+ exit_code=-1,
576
+ error=str(e),
577
+ )
578
+ )
579
+
580
  return self._combine_results(results, main_files)
581
 
582
  async def _execute_c_cpp(
 
820
  return detected_language
821
 
822
 
 
 
 
 
 
 
823
  @app.post("/analyze-inputs")
824
  async def analyze_inputs(code_content: str = Form(...), language: str = Form(...)):
825
  """
 
851
  )
852
  result_dict = analysis_result.model_dump()
853
 
854
+ # Cache the result
855
+
856
  logger.info(f"Input analysis completed for {language} code")
857
  return JSONResponse(content=result_dict)
858
 
 
1044
  "", # for press enter
1045
  ],
1046
  }