зеркало из https://github.com/mozilla/gecko-dev.git
servo: Merge #9688 - Implement Worker#terminate() (fixes #4427) (from KiChjang:worker-terminate); r=jdm
Adds support for terminating DOM workers. A closing flag was added to WorkerGlobalScope per the spec. Rebased #6652, with some comments addressed. Fixes #4427. Source-Repo: https://github.com/servo/servo Source-Revision: c0aa049b0aaf39df979234dff1ed01ff5d9aa5c5
This commit is contained in:
Родитель
27d6b01682
Коммит
b4b5f371df
|
@ -15,7 +15,7 @@ use dom::bindings::reflector::{Reflectable, Reflector};
|
|||
use dom::window::{self, ScriptHelpers};
|
||||
use dom::workerglobalscope::WorkerGlobalScope;
|
||||
use ipc_channel::ipc::IpcSender;
|
||||
use js::jsapi::GetGlobalForObjectCrossCompartment;
|
||||
use js::jsapi::{CurrentGlobalOrNull, GetGlobalForObjectCrossCompartment};
|
||||
use js::jsapi::{JSContext, JSObject, JS_GetClass, MutableHandleValue};
|
||||
use js::{JSCLASS_IS_DOMJSCLASS, JSCLASS_IS_GLOBAL};
|
||||
use msg::constellation_msg::{ConstellationChan, PipelineId};
|
||||
|
@ -147,9 +147,8 @@ impl<'a> GlobalRef<'a> {
|
|||
/// thread.
|
||||
pub fn script_chan(&self) -> Box<ScriptChan + Send> {
|
||||
match *self {
|
||||
GlobalRef::Window(ref window) => {
|
||||
MainThreadScriptChan(window.main_thread_script_chan().clone()).clone()
|
||||
}
|
||||
GlobalRef::Window(ref window) =>
|
||||
MainThreadScriptChan(window.main_thread_script_chan().clone()).clone(),
|
||||
GlobalRef::Worker(ref worker) => worker.script_chan(),
|
||||
}
|
||||
}
|
||||
|
@ -280,11 +279,10 @@ pub fn global_root_from_reflector<T: Reflectable>(reflector: &T) -> GlobalRoot {
|
|||
global_root_from_object(*reflector.reflector().get_jsobject())
|
||||
}
|
||||
|
||||
/// Returns the global object of the realm that the given JS object was created in.
|
||||
/// Returns the Rust global object from a JS global object.
|
||||
#[allow(unrooted_must_root)]
|
||||
pub fn global_root_from_object(obj: *mut JSObject) -> GlobalRoot {
|
||||
pub fn global_root_from_global(global: *mut JSObject) -> GlobalRoot {
|
||||
unsafe {
|
||||
let global = GetGlobalForObjectCrossCompartment(obj);
|
||||
let clasp = JS_GetClass(global);
|
||||
assert!(((*clasp).flags & (JSCLASS_IS_DOMJSCLASS | JSCLASS_IS_GLOBAL)) != 0);
|
||||
match root_from_object(global) {
|
||||
|
@ -300,3 +298,22 @@ pub fn global_root_from_object(obj: *mut JSObject) -> GlobalRoot {
|
|||
panic!("found DOM global that doesn't unwrap to Window or WorkerGlobalScope")
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the global object of the realm that the given JS object was created in.
|
||||
#[allow(unrooted_must_root)]
|
||||
pub fn global_root_from_object(obj: *mut JSObject) -> GlobalRoot {
|
||||
unsafe {
|
||||
let global = GetGlobalForObjectCrossCompartment(obj);
|
||||
global_root_from_global(global)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the global object for the given JSContext
|
||||
#[allow(unrooted_must_root)]
|
||||
pub fn global_root_from_context(cx: *mut JSContext) -> GlobalRoot {
|
||||
unsafe {
|
||||
let global = CurrentGlobalOrNull(cx);
|
||||
assert!(!global.is_null());
|
||||
global_root_from_global(global)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ use dom::bindings::js::{JS, Root};
|
|||
use dom::bindings::refcounted::Trusted;
|
||||
use dom::bindings::reflector::{Reflectable, Reflector};
|
||||
use dom::bindings::utils::WindowProxyHandler;
|
||||
use dom::worker::SharedRt;
|
||||
use encoding::types::EncodingRef;
|
||||
use euclid::length::Length as EuclidLength;
|
||||
use euclid::matrix2d::Matrix2D;
|
||||
|
@ -319,6 +320,7 @@ no_jsmanaged_fields!(AttrIdentifier);
|
|||
no_jsmanaged_fields!(AttrValue);
|
||||
no_jsmanaged_fields!(ElementSnapshot);
|
||||
no_jsmanaged_fields!(HttpsState);
|
||||
no_jsmanaged_fields!(SharedRt);
|
||||
no_jsmanaged_fields!(TouchpadPressurePhase);
|
||||
|
||||
impl JSTraceable for ConstellationChan<ScriptMsg> {
|
||||
|
|
|
@ -9,20 +9,21 @@ use dom::bindings::codegen::Bindings::DedicatedWorkerGlobalScopeBinding;
|
|||
use dom::bindings::codegen::Bindings::DedicatedWorkerGlobalScopeBinding::DedicatedWorkerGlobalScopeMethods;
|
||||
use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull;
|
||||
use dom::bindings::error::ErrorResult;
|
||||
use dom::bindings::global::GlobalRef;
|
||||
use dom::bindings::global::{GlobalRef, global_root_from_context};
|
||||
use dom::bindings::inheritance::Castable;
|
||||
use dom::bindings::js::{Root, RootCollection};
|
||||
use dom::bindings::refcounted::LiveDOMReferences;
|
||||
use dom::bindings::reflector::Reflectable;
|
||||
use dom::bindings::structuredclone::StructuredCloneData;
|
||||
use dom::bindings::trace::JSTraceable;
|
||||
use dom::messageevent::MessageEvent;
|
||||
use dom::worker::{SimpleWorkerErrorHandler, TrustedWorkerAddress, WorkerMessageHandler};
|
||||
use dom::worker::{SimpleWorkerErrorHandler, SharedRt, TrustedWorkerAddress, WorkerMessageHandler};
|
||||
use dom::workerglobalscope::WorkerGlobalScope;
|
||||
use dom::workerglobalscope::WorkerGlobalScopeInit;
|
||||
use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
|
||||
use ipc_channel::router::ROUTER;
|
||||
use js::jsapi::{HandleValue, JSContext, RootedValue};
|
||||
use js::jsapi::{JSAutoCompartment, JSAutoRequest};
|
||||
use js::jsapi::{HandleValue, JS_SetInterruptCallback};
|
||||
use js::jsapi::{JSAutoCompartment, JSAutoRequest, JSContext, RootedValue};
|
||||
use js::jsval::UndefinedValue;
|
||||
use js::rust::Runtime;
|
||||
use msg::constellation_msg::PipelineId;
|
||||
|
@ -33,6 +34,7 @@ use script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort, StackRootTLS, get_
|
|||
use script_traits::{TimerEvent, TimerSource};
|
||||
use std::mem::replace;
|
||||
use std::sync::mpsc::{Receiver, RecvError, Select, Sender, channel};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use url::Url;
|
||||
use util::str::DOMString;
|
||||
use util::thread::spawn_named;
|
||||
|
@ -206,10 +208,12 @@ impl DedicatedWorkerGlobalScope {
|
|||
DedicatedWorkerGlobalScopeBinding::Wrap(cx, scope)
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
pub fn run_worker_scope(init: WorkerGlobalScopeInit,
|
||||
worker_url: Url,
|
||||
id: PipelineId,
|
||||
from_devtools_receiver: IpcReceiver<DevtoolScriptControlMsg>,
|
||||
main_thread_rt: Arc<Mutex<Option<SharedRt>>>,
|
||||
worker: TrustedWorkerAddress,
|
||||
parent_sender: Box<ScriptChan + Send>,
|
||||
own_sender: Sender<(TrustedWorkerAddress, WorkerScriptMsg)>,
|
||||
|
@ -237,6 +241,7 @@ impl DedicatedWorkerGlobalScope {
|
|||
};
|
||||
|
||||
let runtime = new_rt_and_cx();
|
||||
*main_thread_rt.lock().unwrap() = Some(SharedRt::new(&runtime));
|
||||
|
||||
let (devtools_mpsc_chan, devtools_mpsc_port) = channel();
|
||||
ROUTER.route_ipc_receiver_to_mpsc_sender(from_devtools_receiver, devtools_mpsc_chan);
|
||||
|
@ -257,6 +262,15 @@ impl DedicatedWorkerGlobalScope {
|
|||
// registration (#6631), so we instead use a random number and cross our fingers.
|
||||
let scope = global.upcast::<WorkerGlobalScope>();
|
||||
|
||||
unsafe {
|
||||
// Handle interrupt requests
|
||||
JS_SetInterruptCallback(scope.runtime(), Some(interrupt_callback));
|
||||
}
|
||||
|
||||
if scope.is_closing() {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
let _ar = AutoWorkerReset::new(global.r(), worker);
|
||||
scope.execute_script(DOMString::from(source));
|
||||
|
@ -265,6 +279,9 @@ impl DedicatedWorkerGlobalScope {
|
|||
let reporter_name = format!("worker-reporter-{}", random::<u64>());
|
||||
scope.mem_profiler_chan().run_with_memory_reporting(|| {
|
||||
while let Ok(event) = global.receive_event() {
|
||||
if scope.is_closing() {
|
||||
break;
|
||||
}
|
||||
global.handle_event(event);
|
||||
}
|
||||
}, reporter_name, parent_sender, CommonScriptMsg::CollectReports);
|
||||
|
@ -387,6 +404,19 @@ impl DedicatedWorkerGlobalScope {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
unsafe extern "C" fn interrupt_callback(cx: *mut JSContext) -> bool {
|
||||
let global = global_root_from_context(cx);
|
||||
let worker = match global.r() {
|
||||
GlobalRef::Worker(w) => w,
|
||||
_ => panic!("global for worker is not a worker scope")
|
||||
};
|
||||
assert!(worker.is::<DedicatedWorkerGlobalScope>());
|
||||
|
||||
// A false response causes the script to terminate
|
||||
!worker.is_closing()
|
||||
}
|
||||
|
||||
impl DedicatedWorkerGlobalScopeMethods for DedicatedWorkerGlobalScope {
|
||||
// https://html.spec.whatwg.org/multipage/#dom-dedicatedworkerglobalscope-postmessage
|
||||
fn PostMessage(&self, cx: *mut JSContext, message: HandleValue) -> ErrorResult {
|
||||
|
|
|
@ -12,7 +12,7 @@ interface AbstractWorker {
|
|||
// https://html.spec.whatwg.org/multipage/#worker
|
||||
[Constructor(DOMString scriptURL)/*, Exposed=Window,Worker*/]
|
||||
interface Worker : EventTarget {
|
||||
//void terminate();
|
||||
void terminate();
|
||||
|
||||
[Throws]
|
||||
void postMessage(any message/*, optional sequence<Transferable> transfer*/);
|
||||
|
|
|
@ -21,12 +21,15 @@ use dom::eventtarget::EventTarget;
|
|||
use dom::messageevent::MessageEvent;
|
||||
use dom::workerglobalscope::WorkerGlobalScopeInit;
|
||||
use ipc_channel::ipc;
|
||||
use js::jsapi::{HandleValue, JSContext, RootedValue};
|
||||
use js::jsapi::{JSAutoCompartment, JSAutoRequest};
|
||||
use js::jsapi::{HandleValue, JSContext, JSRuntime, RootedValue};
|
||||
use js::jsapi::{JSAutoCompartment, JSAutoRequest, JS_RequestInterruptCallback};
|
||||
use js::jsval::UndefinedValue;
|
||||
use js::rust::Runtime;
|
||||
use script_runtime::ScriptChan;
|
||||
use script_thread::Runnable;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::mpsc::{Sender, channel};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use util::str::DOMString;
|
||||
|
||||
pub type TrustedWorkerAddress = Trusted<Worker>;
|
||||
|
@ -39,21 +42,26 @@ pub struct Worker {
|
|||
/// Sender to the Receiver associated with the DedicatedWorkerGlobalScope
|
||||
/// this Worker created.
|
||||
sender: Sender<(TrustedWorkerAddress, WorkerScriptMsg)>,
|
||||
closing: Arc<AtomicBool>,
|
||||
#[ignore_heap_size_of = "Defined in rust-mozjs"]
|
||||
runtime: Arc<Mutex<Option<SharedRt>>>
|
||||
}
|
||||
|
||||
impl Worker {
|
||||
fn new_inherited(sender: Sender<(TrustedWorkerAddress, WorkerScriptMsg)>)
|
||||
-> Worker {
|
||||
fn new_inherited(sender: Sender<(TrustedWorkerAddress, WorkerScriptMsg)>,
|
||||
closing: Arc<AtomicBool>) -> Worker {
|
||||
Worker {
|
||||
eventtarget: EventTarget::new_inherited(),
|
||||
sender: sender,
|
||||
closing: closing,
|
||||
runtime: Arc::new(Mutex::new(None))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(global: GlobalRef,
|
||||
sender: Sender<(TrustedWorkerAddress, WorkerScriptMsg)>)
|
||||
-> Root<Worker> {
|
||||
reflect_dom_object(box Worker::new_inherited(sender),
|
||||
sender: Sender<(TrustedWorkerAddress, WorkerScriptMsg)>,
|
||||
closing: Arc<AtomicBool>) -> Root<Worker> {
|
||||
reflect_dom_object(box Worker::new_inherited(sender, closing),
|
||||
global,
|
||||
WorkerBinding::Wrap)
|
||||
}
|
||||
|
@ -71,7 +79,8 @@ impl Worker {
|
|||
let scheduler_chan = global.scheduler_chan();
|
||||
|
||||
let (sender, receiver) = channel();
|
||||
let worker = Worker::new(global, sender.clone());
|
||||
let closing = Arc::new(AtomicBool::new(false));
|
||||
let worker = Worker::new(global, sender.clone(), closing.clone());
|
||||
let worker_ref = Trusted::new(worker.r(), global.script_chan());
|
||||
let worker_id = global.get_next_worker_id();
|
||||
|
||||
|
@ -100,18 +109,28 @@ impl Worker {
|
|||
constellation_chan: constellation_chan,
|
||||
scheduler_chan: scheduler_chan,
|
||||
worker_id: worker_id,
|
||||
closing: closing,
|
||||
};
|
||||
|
||||
DedicatedWorkerGlobalScope::run_worker_scope(
|
||||
init, worker_url, global.pipeline(), devtools_receiver, worker_ref,
|
||||
init, worker_url, global.pipeline(), devtools_receiver, worker.runtime.clone(), worker_ref,
|
||||
global.script_chan(), sender, receiver);
|
||||
|
||||
Ok(worker)
|
||||
}
|
||||
|
||||
pub fn is_closing(&self) -> bool {
|
||||
self.closing.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
pub fn handle_message(address: TrustedWorkerAddress,
|
||||
data: StructuredCloneData) {
|
||||
let worker = address.root();
|
||||
|
||||
if worker.is_closing() {
|
||||
return;
|
||||
}
|
||||
|
||||
let global = worker.r().global();
|
||||
let target = worker.upcast();
|
||||
let _ar = JSAutoRequest::new(global.r().get_cx());
|
||||
|
@ -129,6 +148,11 @@ impl Worker {
|
|||
pub fn handle_error_message(address: TrustedWorkerAddress, message: DOMString,
|
||||
filename: DOMString, lineno: u32, colno: u32) {
|
||||
let worker = address.root();
|
||||
|
||||
if worker.is_closing() {
|
||||
return;
|
||||
}
|
||||
|
||||
let global = worker.r().global();
|
||||
let error = RootedValue::new(global.r().get_cx(), UndefinedValue());
|
||||
let errorevent = ErrorEvent::new(global.r(), atom!("error"),
|
||||
|
@ -147,6 +171,19 @@ impl WorkerMethods for Worker {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#terminate-a-worker
|
||||
fn Terminate(&self) {
|
||||
// Step 1
|
||||
if self.closing.swap(true, Ordering::SeqCst) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 4
|
||||
if let Some(runtime) = *self.runtime.lock().unwrap() {
|
||||
runtime.request_interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#handler-worker-onmessage
|
||||
event_handler!(message, GetOnmessage, SetOnmessage);
|
||||
|
||||
|
@ -221,3 +258,26 @@ impl Runnable for WorkerErrorHandler {
|
|||
Worker::handle_error_message(this.addr, this.msg, this.file_name, this.line_num, this.col_num);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct SharedRt {
|
||||
rt: *mut JSRuntime
|
||||
}
|
||||
|
||||
impl SharedRt {
|
||||
pub fn new(rt: &Runtime) -> SharedRt {
|
||||
SharedRt {
|
||||
rt: rt.rt()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
pub fn request_interrupt(&self) {
|
||||
unsafe {
|
||||
JS_RequestInterruptCallback(self.rt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
unsafe impl Send for SharedRt {}
|
||||
|
|
|
@ -18,7 +18,7 @@ use dom::window::{base64_atob, base64_btoa};
|
|||
use dom::workerlocation::WorkerLocation;
|
||||
use dom::workernavigator::WorkerNavigator;
|
||||
use ipc_channel::ipc::IpcSender;
|
||||
use js::jsapi::{HandleValue, JSAutoRequest, JSContext};
|
||||
use js::jsapi::{HandleValue, JSAutoRequest, JSContext, JSRuntime};
|
||||
use js::rust::Runtime;
|
||||
use msg::constellation_msg::{ConstellationChan, PipelineId};
|
||||
use net_traits::{LoadContext, ResourceThread, load_whole_resource};
|
||||
|
@ -29,6 +29,8 @@ use script_traits::{MsDuration, TimerEvent, TimerEventId, TimerEventRequest, Tim
|
|||
use std::cell::Cell;
|
||||
use std::default::Default;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::mpsc::Receiver;
|
||||
use timers::{IsInterval, OneshotTimerCallback, OneshotTimerHandle, OneshotTimers, TimerCallback};
|
||||
use url::Url;
|
||||
|
@ -47,6 +49,7 @@ pub struct WorkerGlobalScopeInit {
|
|||
pub constellation_chan: ConstellationChan<ConstellationMsg>,
|
||||
pub scheduler_chan: IpcSender<TimerEventRequest>,
|
||||
pub worker_id: WorkerId,
|
||||
pub closing: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#the-workerglobalscope-common-interface
|
||||
|
@ -55,6 +58,7 @@ pub struct WorkerGlobalScope {
|
|||
eventtarget: EventTarget,
|
||||
worker_id: WorkerId,
|
||||
worker_url: Url,
|
||||
closing: Arc<AtomicBool>,
|
||||
#[ignore_heap_size_of = "Defined in js"]
|
||||
runtime: Runtime,
|
||||
next_worker_id: Cell<WorkerId>,
|
||||
|
@ -104,6 +108,7 @@ impl WorkerGlobalScope {
|
|||
next_worker_id: Cell::new(WorkerId(0)),
|
||||
worker_id: init.worker_id,
|
||||
worker_url: worker_url,
|
||||
closing: init.closing,
|
||||
runtime: runtime,
|
||||
resource_thread: init.resource_thread,
|
||||
location: Default::default(),
|
||||
|
@ -155,10 +160,18 @@ impl WorkerGlobalScope {
|
|||
self.timers.unschedule_callback(handle);
|
||||
}
|
||||
|
||||
pub fn runtime(&self) -> *mut JSRuntime {
|
||||
self.runtime.rt()
|
||||
}
|
||||
|
||||
pub fn get_cx(&self) -> *mut JSContext {
|
||||
self.runtime.cx()
|
||||
}
|
||||
|
||||
pub fn is_closing(&self) -> bool {
|
||||
self.closing.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
pub fn resource_thread(&self) -> &ResourceThread {
|
||||
&self.resource_thread
|
||||
}
|
||||
|
@ -307,6 +320,9 @@ impl WorkerGlobalScope {
|
|||
self.reflector().get_jsobject(), String::from(source), self.worker_url.serialize(), 1) {
|
||||
Ok(_) => (),
|
||||
Err(_) => {
|
||||
if self.is_closing() {
|
||||
println!("evaluate_script failed (terminated)");
|
||||
} else {
|
||||
// TODO: An error needs to be dispatched to the parent.
|
||||
// https://github.com/servo/servo/issues/6422
|
||||
println!("evaluate_script failed");
|
||||
|
@ -315,6 +331,7 @@ impl WorkerGlobalScope {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn script_chan(&self) -> Box<ScriptChan + Send> {
|
||||
let dedicated =
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
var prev = Date.now()
|
||||
for (var i=0; true; i++) {
|
||||
|
||||
if (i % 100000000 == 0) {
|
||||
var now = Date.now();
|
||||
postMessage(now - prev);
|
||||
prev = now;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
var prev = Date.now()
|
||||
setInterval(function () {
|
||||
var now = Date.now();
|
||||
postMessage(now - prev);
|
||||
prev = now;
|
||||
}, 500);
|
|
@ -0,0 +1,48 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Worker Test</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
var workerPath = (function () {
|
||||
var workerType;
|
||||
switch (window.location.search.match(/worker=(\w+)/)[1]) {
|
||||
case 'block':
|
||||
workerType = 'block';
|
||||
break;
|
||||
default:
|
||||
workerType = 'interval';
|
||||
break;
|
||||
}
|
||||
|
||||
return './worker_post_' + workerType + '.js';
|
||||
})();
|
||||
|
||||
function startWorker() {
|
||||
window.w = new Worker(workerPath);
|
||||
w.onmessage = function(m) {
|
||||
var p = document.createElement('p');
|
||||
p.innerHTML = JSON.stringify(m.data);
|
||||
document.body.appendChild(p);
|
||||
};
|
||||
|
||||
var ps = document.getElementsByTagName('p');
|
||||
while (ps.length) {
|
||||
document.body.removeChild(ps[0]);
|
||||
}
|
||||
}
|
||||
|
||||
function stopWorker() {
|
||||
if (w) {
|
||||
w.terminate();
|
||||
}
|
||||
|
||||
window.w = null;
|
||||
}
|
||||
</script>
|
||||
<button onclick="startWorker()">Start Worker</button>
|
||||
<button onclick="stopWorker()">Stop Worker</button>
|
||||
</body>
|
||||
</html>
|
Загрузка…
Ссылка в новой задаче